This commit is contained in:
H
2025-07-23 20:41:13 +08:00
commit 9c8b1dcef5
49 changed files with 6924 additions and 0 deletions

0
common/__init__.py Normal file
View File

96
common/config.py Normal file
View File

@@ -0,0 +1,96 @@
# -*- coding:utf-8 -*-
# 配置管理模块
import json
import os
import yaml
from typing import Dict, List, Any, Optional
class ConfigManager:
"""配置管理类,负责加载和管理各种配置"""
@staticmethod
def load_json_config(config_path: str) -> Dict:
"""
加载JSON格式的配置文件
Args:
config_path: JSON配置文件路径
Returns:
加载的配置字典
"""
try:
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"加载配置文件 {config_path} 失败: {str(e)}")
return {}
@staticmethod
def load_yaml_config(config_path: str) -> Dict:
"""
加载YAML格式的配置文件
Args:
config_path: YAML配置文件路径
Returns:
加载的配置字典
"""
try:
with open(config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
except Exception as e:
print(f"加载配置文件 {config_path} 失败: {str(e)}")
return {}
@staticmethod
def save_json_config(config_path: str, config_data: Dict) -> bool:
"""
保存配置到JSON文件
Args:
config_path: 目标JSON文件路径
config_data: 要保存的配置数据
Returns:
是否保存成功
"""
try:
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config_data, f, indent=4, ensure_ascii=False)
return True
except Exception as e:
print(f"保存配置到 {config_path} 失败: {str(e)}")
return False
# 默认仓库配置
DEFAULT_REPO_CONFIGS = [
{
"product": "V10-SP3-2403",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP3",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP2",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP1.1",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "mips64el", "loongarch64"],
"repo_types": ["updates"]
},
# 其他仓库配置可以通过配置文件加载...
]

155
common/file_utils.py Normal file
View File

@@ -0,0 +1,155 @@
# -*- coding:utf-8 -*-
# 文件处理工具模块
import os
import bz2
import gzip
import lzma
import shutil
import logging
from typing import Optional, List, Tuple, Union, IO
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger("file_utils")
class FileUtils:
"""文件处理工具类,处理文件操作和压缩文件"""
@staticmethod
def ensure_dir_exists(directory: str) -> bool:
"""
确保目录存在,如果不存在则创建
Args:
directory: 目录路径
Returns:
是否成功创建或已存在
"""
try:
if not os.path.exists(directory):
os.makedirs(directory)
return True
except Exception as e:
logger.error(f"创建目录失败: {directory}, 错误: {str(e)}")
return False
@staticmethod
def extract_bz2(source_path: str, target_path: str) -> bool:
"""
解压BZ2文件
Args:
source_path: BZ2文件路径
target_path: 解压后文件路径
Returns:
是否解压成功
"""
try:
logger.info(f"解压BZ2文件: {source_path} -> {target_path}")
with bz2.BZ2File(source_path) as bz2_file:
data = bz2_file.read()
with open(target_path, 'wb') as f:
f.write(data)
return True
except Exception as e:
logger.error(f"解压BZ2文件失败: {source_path}, 错误: {str(e)}")
return False
@staticmethod
def extract_xz(source_path: str, target_path: str) -> bool:
"""
解压XZ文件
Args:
source_path: XZ文件路径
target_path: 解压后文件路径
Returns:
是否解压成功
"""
try:
logger.info(f"解压XZ文件: {source_path} -> {target_path}")
with lzma.open(source_path, 'rb') as input_file:
with open(target_path, 'wb') as output_file:
shutil.copyfileobj(input_file, output_file)
return True
except Exception as e:
logger.error(f"解压XZ文件失败: {source_path}, 错误: {str(e)}")
return False
@staticmethod
def extract_gz(source_path: str, target_path: str) -> bool:
"""
解压GZ文件
Args:
source_path: GZ文件路径
target_path: 解压后文件路径
Returns:
是否解压成功
"""
try:
logger.info(f"解压GZ文件: {source_path} -> {target_path}")
with gzip.open(source_path, 'rb') as input_file:
with open(target_path, 'wb') as output_file:
shutil.copyfileobj(input_file, output_file)
return True
except Exception as e:
logger.error(f"解压GZ文件失败: {source_path}, 错误: {str(e)}")
return False
@staticmethod
def write_to_file(file_path: str, content: str, mode: str = 'w', encoding: str = 'utf-8') -> bool:
"""
写入内容到文件
Args:
file_path: 文件路径
content: 文件内容
mode: 文件打开模式
encoding: 编码方式
Returns:
是否写入成功
"""
try:
# 确保目录存在
directory = os.path.dirname(file_path)
if directory and not os.path.exists(directory):
os.makedirs(directory)
with open(file_path, mode, encoding=encoding) as f:
f.write(content)
return True
except Exception as e:
logger.error(f"写入文件失败: {file_path}, 错误: {str(e)}")
return False
@staticmethod
def read_file(file_path: str, mode: str = 'r', encoding: Optional[str] = 'utf-8') -> Optional[Union[str, bytes]]:
"""
读取文件内容
Args:
file_path: 文件路径
mode: 文件打开模式
encoding: 编码方式二进制模式下为None
Returns:
文件内容失败返回None
"""
try:
with open(file_path, mode, encoding=encoding) as f:
return f.read()
except Exception as e:
logger.error(f"读取文件失败: {file_path}, 错误: {str(e)}")
return None

103
common/http_utils.py Normal file
View File

@@ -0,0 +1,103 @@
# -*- coding:utf-8 -*-
# HTTP工具模块
import requests
import logging
from typing import Dict, Optional, Union, Any
from urllib.parse import urljoin
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger("http_utils")
class HttpClient:
"""HTTP客户端工具类处理常见的HTTP请求操作"""
DEFAULT_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
@staticmethod
def get(url: str, headers: Optional[Dict[str, str]] = None,
params: Optional[Dict[str, Any]] = None, timeout: int = 30,
verify: bool = False) -> Optional[requests.Response]:
"""
发送GET请求
Args:
url: 请求的URL
headers: 请求头
params: 查询参数
timeout: 超时时间(秒)
verify: 是否验证SSL证书
Returns:
Response对象请求失败返回None
"""
_headers = headers or HttpClient.DEFAULT_HEADERS
try:
logger.debug(f"发送GET请求: {url}")
response = requests.get(url, headers=_headers, params=params,
timeout=timeout, verify=verify)
if response.status_code != 200:
logger.warning(f"请求失败,状态码:{response.status_code}, URL: {url}")
return response
except Exception as e:
logger.error(f"请求异常: {url}, 错误: {str(e)}")
return None
@staticmethod
def download_file(url: str, save_path: str, headers: Optional[Dict[str, str]] = None,
timeout: int = 60, verify: bool = False, chunk_size: int = 8192) -> bool:
"""
下载文件
Args:
url: 文件URL
save_path: 保存路径
headers: 请求头
timeout: 超时时间(秒)
verify: 是否验证SSL证书
chunk_size: 每次读取的块大小
Returns:
是否下载成功
"""
_headers = headers or HttpClient.DEFAULT_HEADERS
try:
logger.info(f"正在下载: {url} -> {save_path}")
response = requests.get(url, headers=_headers, timeout=timeout,
verify=verify, stream=True)
if response.status_code != 200:
logger.error(f"下载失败,状态码: {response.status_code}")
return False
with open(save_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
f.write(chunk)
logger.info(f"下载完成: {save_path}")
return True
except Exception as e:
logger.error(f"下载文件异常: {url}, 错误: {str(e)}")
return False
@staticmethod
def join_url(base_url: str, path: str) -> str:
"""
拼接URL
Args:
base_url: 基础URL
path: 路径
Returns:
完整URL
"""
return urljoin(base_url.rstrip('/') + '/', path.lstrip('/'))

100
common/logger.py Normal file
View File

@@ -0,0 +1,100 @@
# -*- coding:utf-8 -*-
# 日志管理模块
import os
import logging
from typing import Optional
from datetime import datetime
class LoggerManager:
"""日志管理类,用于配置和创建日志器"""
@staticmethod
def setup_logger(name: str, log_file: Optional[str] = None,
level: int = logging.INFO,
format_str: str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
console_output: bool = True) -> logging.Logger:
"""
设置并返回一个日志器
Args:
name: 日志器名称
log_file: 日志文件路径None表示不输出到文件
level: 日志级别
format_str: 日志格式
console_output: 是否输出到控制台
Returns:
配置好的日志器
"""
# 创建日志器
logger = logging.getLogger(name)
logger.setLevel(level)
# 创建格式化器
formatter = logging.Formatter(format_str)
# 如果指定了日志文件
if log_file:
# 确保日志目录存在
log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir)
# 创建文件处理器
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# 如果需要输出到控制台
if console_output:
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
return logger
@staticmethod
def get_timestamped_log_path(base_dir: str, prefix: str, ext: str = '.log') -> str:
"""
生成带时间戳的日志文件路径
Args:
base_dir: 基础目录
prefix: 文件名前缀
ext: 文件扩展名
Returns:
完整的日志文件路径
"""
# 确保基础目录存在
if not os.path.exists(base_dir):
os.makedirs(base_dir)
# 生成时间戳
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# 构建文件名
filename = f"{prefix}_{timestamp}{ext}"
return os.path.join(base_dir, filename)
# 创建一个全局日志器
def get_logger(name: str = 'repo_test', log_file: Optional[str] = None,
level: int = logging.INFO) -> logging.Logger:
"""
获取全局日志器
Args:
name: 日志器名称
log_file: 日志文件路径
level: 日志级别
Returns:
日志器
"""
return LoggerManager.setup_logger(name, log_file, level)
# 默认日志器
default_logger = get_logger()

95
common/rpm.py Normal file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import glob
import xml.etree.ElementTree as ET
import tempfile
import requests
class YumRepoParser:
def __init__(self, config_dir='/etc/yum.repo.d/', config_files=None):
self.config_dir = config_dir
self.config_files = config_files or self._discover_config_files()
def _discover_config_files(self):
"""
自动发现配置文件(*.repo
"""
pattern = os.path.join(self.config_dir, '*.repo')
return glob.glob(pattern)
def parse_repo_metadata(self, repo_path_or_url):
"""
解析指定 yum 仓库的 repodata/primary.xml支持本地路径和远程 http(s) 地址
返回 src 包和二进制包列表
"""
if repo_path_or_url.startswith('http://') or repo_path_or_url.startswith('https://'):
primary_xml_url = repo_path_or_url.rstrip('/') + '/repodata/primary.xml'
with tempfile.NamedTemporaryFile(delete=False) as tmp:
try:
resp = requests.get(primary_xml_url, timeout=30)
resp.raise_for_status()
tmp.write(resp.content)
tmp_path = tmp.name
except Exception as e:
raise RuntimeError(f"下载远程 primary.xml 失败: {primary_xml_url}, 原因: {e}")
else:
tmp_path = os.path.join(repo_path_or_url, 'repodata', 'primary.xml')
if not os.path.exists(tmp_path):
raise FileNotFoundError(f"未找到 primary.xml {repo_path_or_url}")
src_packages = []
bin_packages = []
tree = ET.parse(tmp_path)
root = tree.getroot()
for package in root.findall('.//package'):
pkg_type = package.get('type')
name = package.findtext('name')
location = package.find('location').get('href')
if pkg_type == 'src':
src_packages.append({'name': name, 'location': location})
else:
bin_packages.append({'name': name, 'location': location})
# 清理临时文件(远程下载时)
if repo_path_or_url.startswith('http://') or repo_path_or_url.startswith('https://'):
os.remove(tmp_path)
return src_packages, bin_packages
def get_all_repos(self):
"""
解析所有配置文件,返回所有仓库地址列表,支持 baseurl/mirrorlist/metalink
"""
repos = []
for cfg in self.config_files:
with open(cfg, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith('baseurl='):
url = line.split('=', 1)[1].strip()
repos.append(url)
elif line.startswith('mirrorlist='):
url = line.split('=', 1)[1].strip()
repos.append(url)
elif line.startswith('metalink='):
url = line.split('=', 1)[1].strip()
repos.append(url)
return repos
# 示例用法
if __name__ == "__main__":
parser = YumRepoParser()
repo_urls = parser.get_all_repos()
for repo_url in repo_urls:
# 假设 repo_url 是本地路径,实际可根据需求下载 repodata/primary.xml
try:
src_pkgs, bin_pkgs = parser.parse_repo_metadata(repo_url)
print(f"仓库: {repo_url}")
print(f"源码包: {len(src_pkgs)},二进制包: {len(bin_pkgs)}")
except Exception as e:
print(f"解析失败: {repo_url},原因: {e}")

36
config/fedora/fedora.repo Normal file
View File

@@ -0,0 +1,36 @@
[fedora]
name=Fedora $releasever - $basearch
baseurl=https://mirrors.tuna.tsinghua.edu.cn/fedora/releases/$releasever/Everything/$basearch/os/
#metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch
enabled=1
countme=1
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False
[fedora-debuginfo]
name=Fedora $releasever - $basearch - Debug
baseurl=https://mirrors.tuna.tsinghua.edu.cn/fedora/updates/$releasever/Everything/$basearch/
#metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-debug-$releasever&arch=$basearch
enabled=0
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False
[fedora-source]
name=Fedora $releasever - Source
#baseurl=https://mirrors.tuna.tsinghua.edu.cn/fedora/updates/$releasever/Everything/$basearch/
metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-source-$releasever&arch=$basearch
enabled=0
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False

94
config/repo_configs.json Normal file
View File

@@ -0,0 +1,94 @@
{
"repo_configs": [
{
"product": "V10-SP3-2403",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP3",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP2",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP1.1",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "mips64el", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-HPC",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-ZJ",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-2309A",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/2309A/os/adv/lic/",
"architectures": ["aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-2309B",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/2309B/os/adv/lic/",
"architectures": ["aarch64"],
"repo_types": ["updates"]
},
{
"product": "HostOS",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "Host-2309",
"base_url": "https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "Host-2406",
"base_url": "https://update.cs2c.com.cn/private_test/repo/HOST/2406/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V7.9",
"base_url": "https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V6.10",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V6/V6.10/os/lic/",
"architectures": ["x86_64"],
"repo_types": ["updates"]
},
{
"product": "8U2",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/8U2/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["BaseOS-updates", "AppStream-updates", "PowerTools-updates", "Plus-updates"]
},
{
"product": "8U8",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/8U8/os/adv/lic/",
"architectures": ["x86_64"],
"repo_types": ["BaseOS-updates", "AppStream-updates", "BaseOS", "AppStream", "kernel418", "kernel419"]
}
]
}

145
main.py Normal file
View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# 主执行文件
import os
import sys
import argparse
import logging
import json
from typing import Dict, List, Any
from common.logger import get_logger, LoggerManager
from common.config import ConfigManager, DEFAULT_REPO_CONFIGS
from repodata.checker import RepoChecker
# 设置基本日志级别
logging.basicConfig(level=logging.INFO)
logger = get_logger("main")
def parse_arguments():
parser = argparse.ArgumentParser(description='仓库测试检查工具')
parser.add_argument('--config', type=str, help='配置文件路径')
parser.add_argument('--output-dir', type=str, default='./results', help='输出目录')
parser.add_argument('--max-workers', type=int, default=5, help='最大并行检查线程数')
parser.add_argument('--product', type=str, help='仅检查指定产品')
parser.add_argument('--arch', type=str, help='仅检查指定架构')
parser.add_argument('--verbose', action='store_true', help='显示详细日志')
return parser.parse_args()
def load_config(config_path: str) -> List[Dict[str, Any]]:
"""
加载配置文件
Args:
config_path: 配置文件路径
Returns:
仓库配置列表
"""
# 检查文件是否存在
if not os.path.exists(config_path):
logger.error(f"配置文件不存在: {config_path}")
return DEFAULT_REPO_CONFIGS
# 根据文件扩展名决定加载方式
ext = os.path.splitext(config_path)[1].lower()
if ext == '.json':
config = ConfigManager.load_json_config(config_path)
elif ext in ['.yaml', '.yml']:
config = ConfigManager.load_yaml_config(config_path)
else:
logger.error(f"不支持的配置文件格式: {ext}")
return DEFAULT_REPO_CONFIGS
# 检查配置是否有效
if not config or not isinstance(config, list):
logger.error("无效的配置格式,使用默认配置")
return DEFAULT_REPO_CONFIGS
return config
def filter_configs(configs: List[Dict[str, Any]], product: str = None, arch: str = None) -> List[Dict[str, Any]]:
"""
根据条件过滤仓库配置
Args:
configs: 原始配置列表
product: 产品名称过滤
arch: 架构过滤
Returns:
过滤后的配置列表
"""
if not product and not arch:
return configs
filtered_configs = []
for config in configs:
# 如果指定了产品,但不匹配,则跳过
if product and config["product"] != product:
continue
# 如果指定了架构,需要检查
if arch:
# 克隆配置但只保留指定架构
if arch in config["architectures"]:
new_config = config.copy()
new_config["architectures"] = [arch]
filtered_configs.append(new_config)
else:
# 没有指定架构,保留所有
filtered_configs.append(config)
return filtered_configs
def setup_logging(verbose: bool):
"""设置日志级别"""
level = logging.DEBUG if verbose else logging.INFO
logging.getLogger().setLevel(level)
def main():
"""主函数"""
# 解析命令行参数
args = parse_arguments()
# 设置日志级别
setup_logging(args.verbose)
# 加载配置
if args.config:
logger.info(f"加载配置文件: {args.config}")
repo_configs = load_config(args.config)
else:
logger.info("使用默认配置")
repo_configs = DEFAULT_REPO_CONFIGS
# 过滤配置
filtered_configs = filter_configs(repo_configs, args.product, args.arch)
if not filtered_configs:
logger.error("没有匹配的仓库配置")
return 1
logger.info(f"将检查 {len(filtered_configs)} 个仓库配置")
# 创建输出目录
if not os.path.exists(args.output_dir):
os.makedirs(args.output_dir)
# 创建检查器并执行检查
checker = RepoChecker(args.output_dir)
results = checker.check_all_repositories(filtered_configs, args.max_workers)
# 检查是否有失败
has_failures = any(not result.get("passed", False) for result in results.values())
# 返回状态码
return 1 if has_failures else 0
if __name__ == "__main__":
sys.exit(main())

75
main_test.py Normal file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# 主执行文件
import os
import sys
import argparse
import platform
import logging
from common.logger import get_logger
from common.config import ConfigManager, DEFAULT_REPO_CONFIGS
from repodata.checker import RepoChecker
logger = get_logger("main")
def detect_system_version():
# 示例:根据实际需求完善
sys_info = platform.uname()
# 假设根据发行版和版本号拼接配置文件名
distro = getattr(sys_info, "system", "unknown").lower()
version = getattr(sys_info, "release", "unknown")
config_path = f"config/{distro}_{version}.json"
if os.path.exists(config_path):
return config_path
# 默认配置
return "config/repo_configs.json"
def parse_arguments():
parser = argparse.ArgumentParser(description='仓库测试检查工具')
parser.add_argument('--config', type=str, help='配置文件路径')
parser.add_argument('--output-dir', type=str, default='./results', help='输出目录')
parser.add_argument('--max-workers', type=int, default=5, help='最大并行检查线程数')
parser.add_argument('--product', type=str, help='仅检查指定产品')
parser.add_argument('--arch', type=str, help='仅检查指定架构')
parser.add_argument('--diff', nargs=2, metavar=('A', 'B'), help='对比两个仓库(名称或链接)')
parser.add_argument('--verbose', action='store_true', help='显示详细日志')
return parser.parse_args()
def main():
args = parse_arguments()
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
# 配置文件选择逻辑
if args.config:
config_path = args.config
else:
config_path = detect_system_version()
logger.info(f"自动检测配置文件: {config_path}")
# 加载配置
if os.path.exists(config_path):
repo_configs = ConfigManager.load_json_config(config_path)
else:
logger.warning("未找到配置文件,使用默认配置")
repo_configs = DEFAULT_REPO_CONFIGS
# 过滤配置
# ...根据 args.product 和 args.arch 过滤 repo_configs...
# diff 模式
if args.diff:
repo_a, repo_b = args.diff
# ...实现仓库对比逻辑...
logger.info(f"对比仓库: {repo_a} vs {repo_b}")
# diff 结果输出
return 0
# 常规检查模式
checker = RepoChecker(args.output_dir)
# ...执行检查逻辑...
return 0
if __name__ == "__main__":
sys.exit(main())

114
readme.md Normal file
View File

@@ -0,0 +1,114 @@
# 仓库测试工具套件
## 项目概述
本项目是一个综合性的仓库测试工具套件,用于测试和验证软件仓库的各种特性和功能。它提供了一系列工具来检查仓库数据的一致性、软件包签名、哈希值、依赖关系等。
## 架构设计
项目已重构为模块化架构,将功能划分为不同的模块,便于维护和扩展。
### 目录结构
```
repo_test/
├── script/
│ └── 测试项 #测试脚本
├── common/ # 通用工具模块
│ ├── __init__.py
│ ├── config.py # 配置管理
│ ├── file_utils.py # 文件处理工具
│ ├── http_utils.py # HTTP请求工具
│ └── logger.py # 日志管理
├── config/ # 配置文件目录
│ └── repo_configs.json # 仓库配置
├── repodata/ # 仓库数据检查模块
│ ├── __init__.py
│ ├── checker.py # 仓库检查器
│ └── parser.py # 仓库数据解析
├── main.py # 主执行文件
├── requirements.txt # 依赖列表
└── README.md # 项目说明
```
## 测试项
- 仓库repodata数据校验
- 软件包签名
- 软件包哈希值
- 黑名单二次校验
- 单个软件包安装、卸载
- 源码包下所有二进制包升降级
- 软件包依赖检查
- 软件包兼容性对比
- v10spx 产品升级
- v10spx 仓库软件包对比
## 功能模块
### 1. 仓库数据检查 (repodata)
检查仓库元数据的一致性确保repodata中的软件包列表与实际可下载的软件包一致。
**用法**:
```bash
python main.py --config config/repo_configs.json
```
### 2. 配置管理 (common.config)
用于加载和管理各种配置文件支持JSON、YAML、和yum仓库原生格式。
### 3. HTTP工具 (common.http_utils)
处理HTTP请求和下载功能的工具类。
### 4. 文件工具 (common.file_utils)
处理文件操作和压缩文件的工具类。
### 5. 日志管理 (common.logger)
提供统一的日志记录功能。
## 安装依赖
```bash
pip install -r requirements.txt
```
## 命令行参数
主程序支持以下命令行参数:
- `--config`: 配置文件路径
- `--output-dir`: 输出目录(默认: ./results
- `--max-workers`: 最大并行检查线程数(默认: 5
- `--product`: 仅检查指定产品
- `--arch`: 仅检查指定架构
- `--verbose`: 显示详细日志
## 配置文件格式
配置文件支持JSON和YAML格式示例如下
```json
{
"repo_configs": [
{
"product": "V10-SP3",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
}
]
}
```
## 输出文件
- `repo_rpmlist_check_results.log`: 完整测试结果
- `repo_rpmlist_check_fail.log`: 仅包含失败的测试结果
- `error_*.log`: 各个仓库的详细错误信息

0
repodata/__init__.py Normal file
View File

251
repodata/checker.py Normal file
View File

@@ -0,0 +1,251 @@
# -*- coding:utf-8 -*-
# 仓库检查器模块
import os
import logging
from typing import List, Dict, Any, Optional, Tuple
import concurrent.futures
import time
from common.logger import get_logger
from common.config import ConfigManager
from repodata.parser import RepoDataParser
# 获取日志记录器
logger = get_logger(__name__)
class RepoChecker:
"""仓库检查器,用于检查仓库数据的一致性"""
def __init__(self, output_dir: str = "./results"):
"""
初始化检查器
Args:
output_dir: 输出目录
"""
self.output_dir = output_dir
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 初始化日志文件
self.overall_log_file = os.path.join(output_dir, "repo_rpmlist_check_results.log")
self.fail_log_file = os.path.join(output_dir, "repo_rpmlist_check_fail.log")
# 初始化日志文件
self._init_log_files()
def _init_log_files(self):
"""初始化日志文件"""
# 清空或创建结果文件
with open(self.overall_log_file, 'w', encoding='utf-8') as f:
f.write("=== 整体测试结果 ===
")
with open(self.fail_log_file, 'w', encoding='utf-8') as f:
f.write("=== 失败记录 ===
")
def check_repository(self, repo_config: Dict[str, Any]) -> Dict[str, Any]:
"""
检查单个仓库配置
Args:
repo_config: 仓库配置
Returns:
检查结果
"""
product = repo_config["product"]
base_url = repo_config["base_url"]
results = {}
for arch in repo_config["architectures"]:
for repo_type in repo_config["repo_types"]:
# 构建完整的仓库URL
if "8U8" not in product:
repo_url = f"{base_url}{repo_type}/{arch}/"
else:
repo_url = f"{base_url}{repo_type}/{arch}/os/"
# 生成输出文件名
output_file = os.path.join(self.output_dir, f"rpm_check_{product}_{arch}_{repo_type}.txt")
logger.info(f"开始测试: {product} {arch} {repo_type}")
logger.info(f"测试仓库地址: {repo_url}")
try:
# 创建解析器
parser = RepoDataParser(repo_url)
# 获取HTML中的RPM列表
html = parser.get_html_content()
if not html:
raise Exception("获取HTML内容失败")
rpm_list_html = parser.parse_rpm_list_from_html(html)
# 获取数据库中的RPM列表
sqlite_path = parser.download_and_extract_sqlite()
if not sqlite_path:
raise Exception("获取数据库文件失败")
rpm_list_sqlite = parser.get_rpm_list_from_sqlite(sqlite_path)
# 比较两个列表
comparison_result = parser.compare_rpm_lists(rpm_list_html, rpm_list_sqlite)
formatted_result = parser.format_comparison_result(comparison_result)
# 记录结果
test_passed = comparison_result["is_identical"]
test_key = f"{product}_{arch}_{repo_type}"
results[test_key] = {
"product": product,
"arch": arch,
"repo_type": repo_type,
"repo_url": repo_url,
"passed": test_passed,
"result": formatted_result,
"html_count": comparison_result["list1_count"],
"sqlite_count": comparison_result["list2_count"]
}
# 输出到日志文件
self._write_to_overall_log(product, arch, repo_type, repo_url, formatted_result)
# 如果测试失败,记录到失败日志
if not test_passed:
self._write_to_fail_log(product, arch, repo_type, repo_url, formatted_result)
except Exception as e:
error_msg = f"处理仓库出错: {str(e)}"
logger.error(error_msg)
# 记录错误信息
test_key = f"{product}_{arch}_{repo_type}"
results[test_key] = {
"product": product,
"arch": arch,
"repo_type": repo_type,
"repo_url": repo_url,
"passed": False,
"error": error_msg
}
# 输出到日志文件
self._write_error_to_logs(product, arch, repo_type, repo_url, error_msg)
return results
def check_all_repositories(self, repo_configs: List[Dict[str, Any]], max_workers: int = 5) -> Dict[str, Any]:
"""
并行检查所有仓库
Args:
repo_configs: 仓库配置列表
max_workers: 最大并行工作线程数
Returns:
所有检查结果
"""
all_results = {}
start_time = time.time()
# 使用线程池并行执行检查
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有检查任务
future_to_config = {executor.submit(self.check_repository, config): config for config in repo_configs}
# 收集结果
for future in concurrent.futures.as_completed(future_to_config):
config = future_to_config[future]
try:
result = future.result()
all_results.update(result)
except Exception as e:
logger.error(f"检查仓库 {config['product']} 时出错: {str(e)}")
end_time = time.time()
duration = end_time - start_time
# 记录总结信息
total_tests = len(all_results)
passed_tests = sum(1 for result in all_results.values() if result.get("passed", False))
summary = f"""
====== 测试总结 ======
总测试数: {total_tests}
通过测试: {passed_tests}
失败测试: {total_tests - passed_tests}
测试耗时: {duration:.2f}
"""
logger.info(summary)
# 将总结写入整体日志
with open(self.overall_log_file, 'a', encoding='utf-8') as f:
f.write(summary)
return all_results
def _write_to_overall_log(self, product: str, arch: str, repo_type: str, repo_url: str, result_lines: List[str]):
"""写入结果到整体日志"""
with open(self.overall_log_file, 'a', encoding='utf-8') as f:
f.write(f"
=== {product} {arch} {repo_type} ===
")
f.write(f"Repository URL: {repo_url}
")
for line in result_lines:
f.write(line + '
')
f.write("
")
def _write_to_fail_log(self, product: str, arch: str, repo_type: str, repo_url: str, result_lines: List[str]):
"""写入结果到失败日志"""
with open(self.fail_log_file, 'a', encoding='utf-8') as f:
f.write(f"
=== {product} {arch} {repo_type} ===
")
f.write(f"Repository URL: {repo_url}
")
for line in result_lines:
if "测试通过" not in line:
f.write(line + '
')
f.write("
")
def _write_error_to_logs(self, product: str, arch: str, repo_type: str, repo_url: str, error_msg: str):
"""写入错误信息到日志"""
# 写入到整体日志
with open(self.overall_log_file, 'a', encoding='utf-8') as f:
f.write(f"
=== {product} {arch} {repo_type} ===
")
f.write(f"Repository URL: {repo_url}
")
f.write(f"ERROR: {error_msg}
")
# 写入到失败日志
with open(self.fail_log_file, 'a', encoding='utf-8') as f:
f.write(f"
=== {product} {arch} {repo_type} ===
")
f.write(f"Repository URL: {repo_url}
")
f.write(f"ERROR: {error_msg}
")
# 写入到单独的错误日志
error_log_file = os.path.join(self.output_dir, f"error_{product}_{arch}_{repo_type}.log")
with open(error_log_file, 'w', encoding='utf-8') as f:
f.write(f"Error processing {repo_url}:
{error_msg}")

519
repodata/parser.py Normal file
View File

@@ -0,0 +1,519 @@
# -*- coding:utf-8 -*-
# 仓库数据解析模块
import os
import sqlite3
import logging
import xml.etree.ElementTree as ET
import time
from typing import List, Optional, Dict, Any, Tuple, Set
from bs4 import BeautifulSoup
from urllib.parse import unquote
from common.http_utils import HttpClient
from common.file_utils import FileUtils
# 获取日志记录器
logger = logging.getLogger(__name__)
# XML命名空间
NAMESPACES = {
'common': 'http://linux.duke.edu/metadata/common',
'rpm': 'http://linux.duke.edu/metadata/rpm',
'repo': 'http://linux.duke.edu/metadata/repo'
}
class RepoDataParser:
"""
仓库数据解析器用于解析RPM仓库数据
支持从HTML页面、SQLite数据库和repodata.xml文件中获取软件包列表
"""
def __init__(self, base_url: str):
"""
初始化解析器
Args:
base_url: 仓库基础URL
"""
self.base_url = base_url
self.http_client = HttpClient()
self.file_utils = FileUtils()
self.cache_dir = os.path.join(os.getcwd(), ".cache")
# 确保缓存目录存在
if not os.path.exists(self.cache_dir):
os.makedirs(self.cache_dir)
def get_html_content(self, path: str = "Packages/") -> Optional[str]:
"""
获取HTML内容
Args:
path: 相对路径,默认为"Packages/"
Returns:
HTML内容失败返回None
"""
url = HttpClient.join_url(self.base_url, path)
logger.debug(f"获取HTML内容: {url}")
response = self.http_client.get(url)
if not response:
logger.warning(f"获取HTML内容失败: {url}")
return None
return response.text
def parse_rpm_list_from_html(self, html: str) -> List[str]:
"""
从HTML中解析RPM列表
Args:
html: HTML内容
Returns:
RPM列表
"""
if not html:
logger.warning("HTML内容为空无法解析")
return []
rpmlist = []
try:
soup = BeautifulSoup(html, "html.parser")
raw_links = [item.get('href') for item in soup.find_all('a')]
# 筛选出rpm文件并修正URL编码问题
for link in raw_links:
if link and link.lower().endswith(".rpm"):
# 修正URL编码问题
link = unquote(link)
rpmlist.append(link)
logger.info(f"从HTML解析到{len(rpmlist)}个RPM文件")
return rpmlist
except Exception as e:
logger.error(f"解析HTML内容失败: {str(e)}")
return []
def download_and_extract_sqlite(self) -> Optional[str]:
"""
下载并解压数据库文件
Returns:
解压后的数据库文件路径失败返回None
"""
repodata_url = HttpClient.join_url(self.base_url, "repodata/")
response = self.http_client.get(repodata_url)
if not response:
logger.error(f"获取repodata目录失败: {repodata_url}")
return None
html = response.text
soup = BeautifulSoup(html, "html.parser")
links = [item.get('href') for item in soup.find_all('a')]
# 生成缓存文件名
cache_key = self.base_url.replace('://', '_').replace('/', '_').replace(':', '_')
sqlite_file_path = os.path.join(self.cache_dir, f"{cache_key}_primary.sqlite")
# 如果缓存文件存在且不超过24小时直接返回
if os.path.exists(sqlite_file_path):
file_age = time.time() - os.path.getmtime(sqlite_file_path)
if file_age < 86400: # 24小时 = 86400秒
logger.info(f"使用缓存的SQLite文件: {sqlite_file_path}")
return sqlite_file_path
# 尝试各种数据库文件格式
for link in links:
if not link:
continue
# 处理BZ2格式
if link.endswith("primary.sqlite.bz2"):
sqlite_url = HttpClient.join_url(repodata_url, link)
logger.info(f"发现BZ2数据库文件: {sqlite_url}")
# 下载文件
sqlite_bz2_path = os.path.join(self.cache_dir, f"{cache_key}_primary.sqlite.bz2")
if not self.http_client.download_file(sqlite_url, sqlite_bz2_path):
continue
# 解压文件
if not self.file_utils.extract_bz2(sqlite_bz2_path, sqlite_file_path):
continue
return sqlite_file_path
# 处理XZ格式
elif link.endswith("primary.sqlite.xz"):
sqlite_url = HttpClient.join_url(repodata_url, link)
logger.info(f"发现XZ数据库文件: {sqlite_url}")
# 下载文件
sqlite_xz_path = os.path.join(self.cache_dir, f"{cache_key}_primary.sqlite.xz")
if not self.http_client.download_file(sqlite_url, sqlite_xz_path):
continue
# 解压文件
if not self.file_utils.extract_xz(sqlite_xz_path, sqlite_file_path):
continue
return sqlite_file_path
# 处理GZ格式
elif link.endswith("primary.sqlite.gz"):
sqlite_url = HttpClient.join_url(repodata_url, link)
logger.info(f"发现GZ数据库文件: {sqlite_url}")
# 下载文件
sqlite_gz_path = os.path.join(self.cache_dir, f"{cache_key}_primary.sqlite.gz")
if not self.http_client.download_file(sqlite_url, sqlite_gz_path):
continue
# 解压文件
if not self.file_utils.extract_gz(sqlite_gz_path, sqlite_file_path):
continue
return sqlite_file_path
logger.error(f"未找到支持的数据库文件: {repodata_url}")
return None
def get_rpm_list_from_sqlite(self, sqlite_path: str) -> List[str]:
"""
从SQLite数据库获取RPM列表
Args:
sqlite_path: SQLite数据库文件路径
Returns:
RPM列表
"""
if not os.path.exists(sqlite_path):
logger.error(f"SQLite数据库文件不存在: {sqlite_path}")
return []
rpmlist = []
try:
# 连接数据库
conn = sqlite3.connect(sqlite_path)
cursor = conn.cursor()
# 查询软件包信息
cursor.execute("""
SELECT name, version, release, epoch, arch
FROM packages
ORDER BY name
""")
# 生成RPM文件名
for row in cursor:
name, version, release, epoch, arch = row
# 处理epoch
epoch_str = "" if not epoch or epoch == "0" else f"{epoch}:"
rpm = f"{name}-{epoch_str}{version}-{release}.{arch}.rpm"
rpmlist.append(rpm)
logger.info(f"从SQLite获取到{len(rpmlist)}个RPM文件")
# 关闭连接
cursor.close()
conn.close()
return rpmlist
except Exception as e:
logger.error(f"从SQLite获取RPM列表失败: {str(e)}")
return []
def download_repodata_xml(self) -> Optional[str]:
"""
下载repodata.xml文件
Returns:
下载的文件路径失败返回None
"""
# 首先尝试获取repomd.xml
repomd_url = HttpClient.join_url(self.base_url, "repodata/repomd.xml")
logger.info(f"尝试下载repomd.xml: {repomd_url}")
# 生成缓存文件名
cache_key = self.base_url.replace('://', '_').replace('/', '_').replace(':', '_')
repomd_path = os.path.join(self.cache_dir, f"{cache_key}_repomd.xml")
# 下载repomd.xml
if not self.http_client.download_file(repomd_url, repomd_path):
logger.error(f"下载repomd.xml失败: {repomd_url}")
return None
# 解析repomd.xml以找到primary.xml的位置
try:
tree = ET.parse(repomd_path)
root = tree.getroot()
# 查找primary.xml的位置
primary_location = None
for data_element in root.findall('.//{http://linux.duke.edu/metadata/repo}data'):
if data_element.get('type') == 'primary':
location_element = data_element.find('.//{http://linux.duke.edu/metadata/repo}location')
if location_element is not None:
primary_location = location_element.get('href')
break
if not primary_location:
logger.error("在repomd.xml中未找到primary.xml的位置")
return None
# 下载primary.xml
primary_url = HttpClient.join_url(self.base_url, primary_location)
logger.info(f"找到primary.xml位置: {primary_url}")
# 检查是否是压缩文件
primary_path = os.path.join(self.cache_dir, f"{cache_key}_primary.xml")
compressed_path = None
if primary_location.endswith('.gz'):
compressed_path = os.path.join(self.cache_dir, f"{cache_key}_primary.xml.gz")
if not self.http_client.download_file(primary_url, compressed_path):
logger.error(f"下载primary.xml.gz失败: {primary_url}")
return None
if not self.file_utils.extract_gz(compressed_path, primary_path):
logger.error(f"解压primary.xml.gz失败")
return None
elif primary_location.endswith('.bz2'):
compressed_path = os.path.join(self.cache_dir, f"{cache_key}_primary.xml.bz2")
if not self.http_client.download_file(primary_url, compressed_path):
logger.error(f"下载primary.xml.bz2失败: {primary_url}")
return None
if not self.file_utils.extract_bz2(compressed_path, primary_path):
logger.error(f"解压primary.xml.bz2失败")
return None
elif primary_location.endswith('.xz'):
compressed_path = os.path.join(self.cache_dir, f"{cache_key}_primary.xml.xz")
if not self.http_client.download_file(primary_url, compressed_path):
logger.error(f"下载primary.xml.xz失败: {primary_url}")
return None
if not self.file_utils.extract_xz(compressed_path, primary_path):
logger.error(f"解压primary.xml.xz失败")
return None
else:
# 直接下载未压缩的XML
if not self.http_client.download_file(primary_url, primary_path):
logger.error(f"下载primary.xml失败: {primary_url}")
return None
return primary_path
except Exception as e:
logger.error(f"处理repomd.xml时出错: {str(e)}")
return None
def get_rpm_list_from_xml(self, xml_path: str) -> List[str]:
"""
从primary.xml文件获取RPM列表
Args:
xml_path: primary.xml文件路径
Returns:
RPM列表
"""
if not os.path.exists(xml_path):
logger.error(f"XML文件不存在: {xml_path}")
return []
rpmlist = []
try:
# 解析XML文件
tree = ET.parse(xml_path)
root = tree.getroot()
# 查找所有包元素
for pkg_element in root.findall('.//{http://linux.duke.edu/metadata/common}package'):
if pkg_element.get('type') != 'rpm':
continue
# 获取包名
name_element = pkg_element.find('.//{http://linux.duke.edu/metadata/common}name')
if name_element is None:
continue
name = name_element.text
# 获取版本信息
version_element = pkg_element.find('.//{http://linux.duke.edu/metadata/common}version')
if version_element is None:
continue
epoch = version_element.get('epoch', '0')
version = version_element.get('ver', '')
release = version_element.get('rel', '')
# 获取架构
arch_element = pkg_element.find('.//{http://linux.duke.edu/metadata/common}arch')
if arch_element is None:
continue
arch = arch_element.text
# 处理epoch
epoch_str = "" if not epoch or epoch == "0" else f"{epoch}:"
# 生成RPM文件名
rpm = f"{name}-{epoch_str}{version}-{release}.{arch}.rpm"
rpmlist.append(rpm)
logger.info(f"从XML获取到{len(rpmlist)}个RPM文件")
return rpmlist
except Exception as e:
logger.error(f"从XML获取RPM列表失败: {str(e)}")
return []
def compare_rpm_lists(self, list1: List[str], list2: List[str]) -> Dict[str, Any]:
"""
比较两个RPM列表的差异
Args:
list1: 第一个RPM列表
list2: 第二个RPM列表
Returns:
比较结果
"""
# 标准化RPM名称去除版本信息只保留包名
def normalize_rpm_name(rpm: str) -> str:
# 移除.rpm后缀
if rpm.lower().endswith('.rpm'):
rpm = rpm[:-4]
return rpm
# 转换为集合以便比较
set1 = set(list1)
set2 = set(list2)
# 计算差异
only_in_list1 = sorted(set1 - set2)
only_in_list2 = sorted(set2 - set1)
# 检查是否完全相同
is_identical = len(only_in_list1) == 0 and len(only_in_list2) == 0
# 构建结果
result = {
"is_identical": is_identical,
"only_in_list1": only_in_list1,
"only_in_list2": only_in_list2,
"list1_count": len(list1),
"list2_count": len(list2),
"common_count": len(set1.intersection(set2))
}
return result
def format_comparison_result(self, result: Dict[str, Any]) -> List[str]:
"""
格式化比较结果
Args:
result: 比较结果字典
Returns:
格式化后的文本行列表
"""
output_lines = []
# 添加统计信息
output_lines.append(f"列表1软件包数量: {result['list1_count']}")
output_lines.append(f"列表2软件包数量: {result['list2_count']}")
output_lines.append(f"共同软件包数量: {result.get('common_count', 0)}")
output_lines.append("")
if result["is_identical"]:
output_lines.append("两个软件包列表完全相同")
output_lines.append("#####【测试通过】#####")
return output_lines
# 报告第一个列表独有的项目
if result["only_in_list1"]:
output_lines.append(f"仅在第一个列表中存在的软件包({len(result['only_in_list1'])}个):")
output_lines.extend(result["only_in_list1"])
else:
output_lines.append("没有仅在第一个列表中存在的软件包。")
output_lines.append("")
# 报告第二个列表独有的项目
if result["only_in_list2"]:
output_lines.append(f"仅在第二个列表中存在的软件包({len(result['only_in_list2'])}个):")
output_lines.extend(result["only_in_list2"])
else:
output_lines.append("没有仅在第二个列表中存在的软件包。")
return output_lines
def get_all_rpm_sources(self) -> Dict[str, List[str]]:
"""
获取所有可用的RPM列表来源
Returns:
包含不同来源RPM列表的字典
"""
results = {}
# 1. 从HTML获取RPM列表
html = self.get_html_content()
if html:
rpm_list_html = self.parse_rpm_list_from_html(html)
results["html"] = rpm_list_html
# 2. 从SQLite获取RPM列表
sqlite_path = self.download_and_extract_sqlite()
if sqlite_path:
rpm_list_sqlite = self.get_rpm_list_from_sqlite(sqlite_path)
results["sqlite"] = rpm_list_sqlite
# 3. 从XML获取RPM列表
xml_path = self.download_repodata_xml()
if xml_path:
rpm_list_xml = self.get_rpm_list_from_xml(xml_path)
results["xml"] = rpm_list_xml
return results
def compare_all_sources(self) -> Dict[str, Any]:
"""
比较所有来源的RPM列表
Returns:
比较结果
"""
sources = self.get_all_rpm_sources()
if not sources:
logger.error("没有找到任何RPM列表来源")
return {"error": "没有找到任何RPM列表来源"}
results = {}
# 如果有HTML和SQLite来源比较它们
if "html" in sources and "sqlite" in sources:
html_vs_sqlite = self.compare_rpm_lists(sources["html"], sources["sqlite"])
results["html_vs_sqlite"] = html_vs_sqlite
# 如果有HTML和XML来源比较它们
if "html" in sources and "xml" in sources:
html_vs_xml = self.compare_rpm_lists(sources["html"], sources["xml"])
results["html_vs_xml"] = html_vs_xml
# 如果有SQLite和XML来源比较它们
if "sqlite" in sources and "xml" in sources:
sqlite_vs_xml = self.compare_rpm_lists(sources["sqlite"], sources["xml"])
results["sqlite_vs_xml"] = sqlite_vs_xml
return results

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
# 项目依赖
requests>=2.25.0
beautifulsoup4>=4.9.3
lxml>=4.6.3
PyYAML>=5.4.1

View File

@@ -0,0 +1,27 @@
# 测试项1 - repodata 检查
## 脚本简介
本脚本用于批量测试每周更新仓库的repodata是否正常。
## 使用方法
python3 repo_rpmlist_check_all.py
## 输入
repo_configs1
1.每周仓库测试仓库地址 一般情况下不需要变更,可直接检查所有更新仓库;
2.可修改reo_config1中仓库为待其他测试仓库地址
3.需注意仓库地址链接(eg:8U8)与其他仓库地址链接不一致;
## 输出文件
完整日志repo_rpmlist_check_results.log
失败记录repo_rpmlist_check_fail.log
## 脚本流程说明
脚本通过获取repodata内的数据库文件得到rpmlist 与网页解析获得rpmlist进行对比
内网python3 环境,即可运行;

View File

@@ -0,0 +1,354 @@
# -*- coding:utf-8 -*-
# Time:2025/4/21 17:46
# Author:GuoChao
# FileName:repo_rpmlist_check.py
from bs4 import BeautifulSoup
import bz2
import lzma
import shutil
import sqlite3
import requests
from lxml import etree
import logging
logging.captureWarnings(True)
import gzip
def Gethtml(baseurl):
path = f"{baseurl}/Packages/"
head = {
"User-Agent": "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122 Safari / 537.36"
}
response = requests.get(path, headers=head, verify=False)
html = response.text
return html
#使用bs4 进行网页解析,获取软件包列表
def Getrpmlist(html):
rpmlist1=[]
rpmlist=[]
soup=BeautifulSoup(html,"html.parser")
for item in soup.find_all('a'):
rpmlist1.append(item['href'])
for i in rpmlist1: # 对url不能识别”+“进行修正
if ".rpm" in i:
i = i.replace("%2B", "+")
rpmlist.append(i)
print('\n' +"通过html页面获取软件包 ",len(rpmlist))
return rpmlist
def Getsqlite(baseurl):
"""
从repodata中获取数据库压缩文件下载并解压
url 格式 例如 baseurl=https://update.cs2c.com.cn/NS/V10/8U6/os/adv/lic/AppStream/x86_64/os/
"""
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
path = f"{baseurl}/repodata/"
try:
response = requests.get(path, headers=head, verify=False, timeout=10)
if response.status_code != 200:
print(f"请求失败,状态码:{response.status_code}")
return None
html = response.text
html = etree.HTML(html)
a_links = html.xpath("//a")
for item in a_links:
item1 = item.get('href')
if item1.endswith("primary.sqlite.bz2"):
sqlite_url = '/'.join([path, item1])
print(f"正在下载:{sqlite_url}")
response = requests.get(sqlite_url, headers=head, verify=False, timeout=10)
if response.status_code != 200:
print(f"下载失败,状态码:{response.status_code}")
return None
un_path = "repotest.sqlite.bz2"
with open(un_path, 'wb') as code:
code.write(response.content)
with bz2.BZ2File(un_path) as bz2file:
data = bz2file.read()
newfilepath = "repotest.sqlite"
with open(newfilepath, 'wb') as f:
f.write(data)
return newfilepath
elif item1.endswith("primary.sqlite.xz"):
sqlite_url = '/'.join([path, item1])
print(f"正在下载:{sqlite_url}")
response = requests.get(sqlite_url, headers=head, verify=False, timeout=100)
if response.status_code != 200:
print(f"下载失败,状态码:{response.status_code}")
return None
un_path = "repotest.sqlite.xz"
with open(un_path, 'wb') as code:
code.write(response.content)
with lzma.open(un_path, 'rb') as input:
with open("repotest.sqlite", 'wb') as output:
shutil.copyfileobj(input, output)
return "repotest.sqlite"
elif item1.endswith("primary.sqlite.gz"):
sqlite_url = '/'.join([path, item1])
print(f"正在下载:{sqlite_url}")
response = requests.get(sqlite_url, headers=head, verify=False, timeout=10)
if response.status_code != 200:
print(f"下载失败,状态码:{response.status_code}")
return None
un_path = "repotest.sqlite.gz"
with open(un_path, 'wb') as code:
code.write(response.content)
with gzip.open(un_path, 'rb') as input:
with open("repotest.sqlite", 'wb') as output:
shutil.copyfileobj(input, output)
return "repotest.sqlite"
print("获取数据库文件失败,请检查!")
return None
except Exception as e:
print(f"发生异常:{e}")
return None
def Getrpminfo(database):
"""
连接sqlite数据库获取软件包信息
"""
if database is None:
print("database 错误")
con = sqlite3.connect(database)
##读取sqlite数据
cursor = con.cursor()
##创建游标cursor来执行execute语句
cursor.execute("SELECT * from packages")
content = cursor.execute("SELECT name, version, epoch, release, arch, rpm_sourcerpm, pkgId from packages")
rpmlist=[]
for row in cursor:
rpm=row[0] + "-" + row[1] + "-" + row[3] + "." + row[4] + ".rpm"
rpmlist.append(rpm)
print('\n' + '通过sqlite获取软件包', len(rpmlist))
return rpmlist
cursor.close()
con.close()
def compare_package_lists(package_list1, package_list2):
set1 = set(package_list1)
set2 = set(package_list2)
only_in_list1 = set1 - set2
only_in_list2 = set2 - set1
result = []
if only_in_list1:
result.append("仅在第一个列表中存在的软件包:")
result.extend(sorted(only_in_list1))
else:
result.append("没有仅在第一个列表中存在的软件包。")
if only_in_list2:
result.append("仅在第二个列表中存在的软件包:")
result.extend(sorted(only_in_list2))
else:
result.append("没有仅在第二个列表中存在的软件包。")
if not only_in_list1 and not only_in_list2:
result.append('\n' +"两个软件包列表完全相同"+'\n'+"#####【测试通过】#####")
for line in result:
print(line)
return result
def save_comparison_result_to_file(result, file_path):
try:
with open(file_path, 'w', encoding='utf-8') as file:
for line in result:
file.write(line + '\n')
print(f"对比结果已成功保存到 {file_path}")
except Exception as e:
print(f"保存结果到文件时出现错误:{e}")
if __name__ == "__main__":
repo_configs1 = [
{
"product": "V10-SP3-2403",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP3",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP2",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-SP1.1",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/",
"architectures": ["x86_64", "aarch64", "mips64el", "loongarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-HPC",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-ZJ",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-2309A",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/2309A/os/adv/lic/",
"architectures": ["aarch64"],
"repo_types": ["updates"]
},
{
"product": "V10-2309B",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/2309B/os/adv/lic/",
"architectures": ["aarch64"],
"repo_types": ["updates"]
},
{
"product": "HostOS",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "Host-2309",
"base_url": "https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "Host-2406",
"base_url": "https://update.cs2c.com.cn/private_test/repo/HOST/2406/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V7.9",
"base_url": "https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["updates"]
},
{
"product": "V6.10",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V6/V6.10/os/lic/",
"architectures": ["x86_64"],
"repo_types": ["updates"]
},
{
"product": "8U2",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/8U2/os/adv/lic/",
"architectures": ["x86_64", "aarch64"],
"repo_types": ["BaseOS-updates", "AppStream-updates", "PowerTools-updates", "Plus-updates"]
},
{
"product": "8U8",
"base_url": "https://update.cs2c.com.cn/private_test/repo/V10/8U8/os/adv/lic/",
"architectures": ["x86_64"],
"repo_types": ["BaseOS-updates", "AppStream-updates", "BaseOS", "AppStream","/kernel418","/kernel419"]
}
]
if __name__ == "__main__":
# 初始化结果记录文件
overall_result_file = "./repo_rpmlist_check_results.log"
fail_log_file = "./repo_rpmlist_check_fail.log"
# 清空或创建结果文件
with open(overall_result_file, 'w', encoding='utf-8') as f:
f.write("=== 整体测试结果 ===\n\n")
with open(fail_log_file, 'w', encoding='utf-8') as f:
f.write("=== 失败记录 ===\n\n")
for config in repo_configs1:
product = config["product"]
base_url = config["base_url"]
for arch in config["architectures"]:
for repo_type in config["repo_types"]:
# Construct full repository URL
#repo_url = f"{base_url}{repo_type}/{arch}/" # 通用
# repo_url = f"{base_url}{repo_type}/{arch}/os/" # ns8.8 使用
if "8U8" not in product:
repo_url = f"{base_url}{repo_type}/{arch}/"
else:
repo_url = f"{base_url}{repo_type}/{arch}/os/"
# Generate output filename
output_file = f"./rpm_check_{product}_{arch}_{repo_type}.txt"
print(f"\n#####开始测试: {product} {arch} {repo_type}#####")
print(f"测试仓库地址: {repo_url}")
try:
# Get RPM lists and compare
rpmlist1 = Getrpmlist(Gethtml(repo_url))
rpmlist2 = Getrpminfo(Getsqlite(repo_url))
result = compare_package_lists(rpmlist1, rpmlist2)
# Save full results to individual file
# save_comparison_result_to_file(result, output_file)
# 记录到整体结果文件
with open(overall_result_file, 'a', encoding='utf-8') as f:
f.write(f"\n=== {product} {arch} {repo_type} ===\n")
f.write(f"Repository URL: {repo_url}\n")
for line in result:
f.write(line + '\n')
f.write("\n")
# 检查是否有失败如果有则记录到fail.log
if "测试通过" not in '\n'.join(result):
with open(fail_log_file, 'a', encoding='utf-8') as f:
f.write(f"\n=== {product} {arch} {repo_type} ===\n")
f.write(f"Repository URL: {repo_url}\n")
for line in result:
if "测试通过" not in line:
f.write(line + '\n')
f.write("\n")
except Exception as e:
error_msg = f"Error processing {product} {arch} {repo_type}: {str(e)}"
print(error_msg)
# 记录错误到整体结果文件和fail.log
with open(overall_result_file, 'a', encoding='utf-8') as f:
f.write(f"\n=== {product} {arch} {repo_type} ===\n")
f.write(f"Repository URL: {repo_url}\n")
f.write(f"ERROR: {error_msg}\n\n")
with open(fail_log_file, 'a', encoding='utf-8') as f:
f.write(f"\n=== {product} {arch} {repo_type} ===\n")
f.write(f"Repository URL: {repo_url}\n")
f.write(f"ERROR: {error_msg}\n\n")
# 仍然保存错误到单独文件
with open(f"./error_{product}_{arch}_{repo_type}.log", "w") as f:
f.write(f"Error processing {repo_url}:\n{error_msg}")
print(f"\n所有测试完成,整体结果已保存到 {overall_result_file}")
print(f"失败记录已保存到 {fail_log_file}")

View File

@@ -0,0 +1,37 @@
测试项1 - 安装包签名检查
## 脚本简介
本脚本用于批量测试软件包签名的方式
## 使用方法
# ./check_pgpsig.sh
---
## 输入文件
- `pkg`:每行一个 RPM 包名,。
---
## 输出文件
- 没有签名的软件包名
---
## 脚本流程说明
1. 使用 find 命令递归查找当前目录下的所有 RPM 文件
2. 使用 rpm -qp 命令查询 RPM 包的头信息
3. --qf 选项指定输出格式,包含包名 (%{NAME}) 和 GPG 签名信息 (%{RSAHEADER:pgpsig})
4. 如果签名信息中包含 "none",表示未签名,将结果同时输出到标准输出和 nogpg.list 文件。
5. 否则,表示已签名,将结果追加到 gpg.list 文件。
---
## 注意事项
- 需要 root 权限运行。
- 系统需启用 YUM 并配置好可用仓库。
- 安装失败或卸载失败的错误日志会保存在 `Error` 字段中,方便后续排查。
---

View File

@@ -0,0 +1,11 @@
#!/bin/bash
LANG=C
for pkg in `find . -type f -name '*.rpm'`
do
RES=`rpm -qp --qf '%{NAME} %{RSAHEADER:pgpsig}\n' $pkg`
if echo $RES | grep 'none';then
echo $RES | tee -a nogpg.list
else
echo $RES >> gpg.list
fi
done

View File

@@ -0,0 +1,38 @@
测试项1 - 安装包哈希值
## 脚本简介
这个 Bash 脚本的主要功能是递归查找当前目录下的所有 RPM 包文件,然后比较每个 RPM 包的本地 MD5 签名与 Koji 构建系统中记录的 MD5 签名是否一致
## 使用方法
# ./check_pgpsig.sh
---
## 输入文件
- `pkg`:每行一个 RPM 包名,。
-使用 find 命令递归查找当前目录下的所有 RPM 文件
---
## 输出文件
-MD5 值匹配的和不匹配的,并分别将结果输出到 equal.txt 和 noquual.txt 文件中
---
## 脚本流程说明
1. 使用 find 命令递归查找当前目录下的所有 RPM 文件
2. 使用 rpm -qp 命令查询 RPM 包的头信息
3. --qf 选项指定输出格式,包含包名 (%{NAME}) 和 GPG 签名信息 (%{RSAHEADER:pgpsig})
4. 如果签名信息中包含 "none",表示未签名,将结果同时输出到标准输出和 nogpg.list 文件。
5. 否则,表示已签名,将结果追加到 gpg.list 文件。
---
## 注意事项
- 需要 root 权限运行。
- 系统需启用 YUM 并配置好可用仓库。
- 安装失败或卸载失败的错误日志会保存在 `Error` 字段中,方便后续排查。
---

View File

@@ -0,0 +1,31 @@
#!/bin/bash
# 定义要处理的文件夹路径
for rpm_file in $(find . -type f -name '*.rpm'); do
# 去除 ./ 前缀
clean_rpm_file="${rpm_file#./}"
# 检查文件是否存在
if [ -f "$clean_rpm_file" ]; then
# 执行 rpm 命令获取 MD5 值
rpm_md5=$(rpm -qp --queryformat '%{SIGMD5}\n' "$clean_rpm_file" 2>&1 | awk '/^[0-9a-fA-F]{32}$/ {print $0}')
echo $rpm_md5
# 执行 koji 命令获取 MD5 值
koji_md5=$(koji rpminfo "$clean_rpm_file" | grep 'SIGMD5:' | awk '{print $2}')
echo $koji_md5
# 比较两个 MD5 值
if [ "$rpm_md5" = "$koji_md5" ]; then
echo "File: $clean_rpm_file" >> equal.txt
echo "rpm MD5: $rpm_md5" >> equal.txt
echo "koji MD5: $koji_md5" >> equal.txt
echo "------------------------" >> equal.txt
else
echo "File: $clean_rpm_file" >> noquual.txt
echo "rpm MD5: $rpm_md5" >> noquual.txt
echo "koji MD5: $koji_md5" >> noquual.txt
echo "------------------------" >> noquual.txt
fi
fi
done

View File

@@ -0,0 +1,62 @@
# 测试项2 - 安装卸载测试脚本说明
## 脚本简介
本脚本用于批量测试指定架构和产品下的软件包是否能被 **成功安装****成功卸载**
读取 `Pkglist` 中的包名,分别进行安装和卸载,并记录详细日志与结果。
## 使用方法
# sh test.sh <PRODUCT_NAME> <ARCH>
示例:
# sh test.sh V10 x86_64
---
## 输入文件
- `Pkglist`:文本文件,每行一个 RPM 包名,作为测试目标(不包含 `.rpm` 后缀)。
---
## 输出文件
- `测试项2-<PRODUCT_NAME>_<ARCH>_install_results.csv`
安装卸载测试结果表格,包含四列:
- `Package`:软件包名
- `Install Status`:安装是否成功(成功/失败)
- `Remove Status`:卸载是否成功(成功/失败)
- `Error`:失败时的错误信息
- `测试项2-<PRODUCT_NAME>_<ARCH>_install_test.log`
完整安装卸载过程日志。
---
## 脚本流程说明
1. 读取参数 `<PRODUCT_NAME>``<ARCH>`
2. 逐行读取 `Pkglist` 中的软件包名。
3. 对每个包执行:
- `yum install -y <pkg>`(记录安装状态)
- 安装成功后再执行 `yum remove -y <pkg>`(记录卸载状态)
4. 写入最终 CSV 与日志文件。
---
## 注意事项
- 需要 root 权限运行。
- 系统需启用 YUM 并配置好可用仓库。
- 安装失败或卸载失败的错误日志会保存在 `Error` 字段中,方便后续排查。
---
compare-0522.py
## 脚本简介
本脚本生成仓库测试的多个脚本,安装本周更新的的软件包以及多版本升降级脚本,把每个产品都生成一个文件夹供测试使用
## 使用方法
# python3 compare-0522.py
生成文件内容:
install_remove_test.sh (安装测试脚本)
Pkglist-V10SP4-x86 (本周仓库更新的二进制包列表)
V10SP3-2403-x86分析报告-分析报告-20250527-1406.xlsx (仓库分析报告)
test_upgrade_downgrade_V10SP3-2403-x86--20250527-1406.sh (本周更新包升降级测试脚本)

View File

@@ -0,0 +1,91 @@
# compare.py 使用说明文档
## 脚本简介
该脚本用于比较测试仓库与发布仓库新增/减少的软件包的构建差异:
- 自动下载并解压 `primary.sqlite` 数据库文件
- 提取 SRPM 与 Binary NVR 信息
- 分析多个仓库中同一 SRPM 的二进制构建差异
- 输出本周重点分析的 SRPM 差异(新增/减少)
- 输出多仓库整体 SRPM 构建分布情况到 Excel 文件
---
## 使用方法
python3 compare.py
脚本默认读取当前目录下的 `config.ini` 配置文件。
---
## 配置说明
`config.ini` 配置格式如下:
[V10sp4-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/
filename = V10SP3-2403-x86分析报告
[V10sp3-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/x86_64/
filename = V10SP3-x86分析报告
支持多个产品段落,每个段落会分别输出一个分析结果。
---
## 输出内容说明
脚本会输出一个 `.xlsx` 文件,包含以下 Sheet
1. `仓库所有SRPM分析`:列出所有仓库中 SRPM → Binary 包映射与差异信息
2. `外网仓库本周重点分析的SRPM`:筛选重点 SRPM 在 base 与 updates 仓库中的构建差异
3. `测试仓库本周重点分析的SRPM`:筛选重点 SRPM 在 base 与 updates_test 仓库中的构建差异
4. `测试仓库对比`:展示 updates 与 updates_test 仓库中新增/减少的 SRPM 与 Binary 包
5. `仓库原始数据`(可选):包含各仓库原始包信息
---
## 分析字段说明
输出的主要字段如下:
- `Repo`:仓库名
- `SRPM Name`:源码包名(无版本)
- `SRPM Version`源码包版本号version-release
- `Binary Package List`:构建出的二进制包名(不含版本)
- `Binary NVR List`:完整二进制包 NVR
- `Binary Count`:该 SRPM 构建的二进制包数量
- `Binary Count Changed`是否存在包数量变动Yes/No
- `Added Binaries` / `Removed Binaries`:相对于其他版本新增/减少的二进制包名
---
## 依赖组件
- Python 3.x
- `requests`
- `lxml`
- `pandas`
- `openpyxl`
安装依赖示例:
```bash
pip install requests lxml pandas openpyxl
```
---
## 示例输出文件
输出文件名格式如下:
```
<ProductName>-差异分析-YYYYMMDD-HHMM.xlsx
```

View File

@@ -0,0 +1,371 @@
from collections import defaultdict
import bz2
import lzma
import shutil
import sqlite3
import requests
from lxml import etree
import configparser
import datetime
import pandas as pd
import openpyxl
import gzip
import os
from openpyxl.styles import Font, PatternFill
import urllib3
from test_srpm_up_down import gen_updown_test
#from generate_test_summary import generate_test_summary_excel
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ---------------------
# 下载 primary.sqlite 并解压
# ---------------------
def Getsqlite_primary(baseurl):
head = {
"User-Agent": "Mozilla/5.0"
}
path = f"{baseurl}repodata/"
try:
response = requests.get(path, headers=head, verify=False)
html = response.text
html = etree.HTML(html)
a_links = html.xpath("//a")
for item in a_links:
item1 = item.get('href')
if item1.endswith("primary.sqlite.bz2"):
sqlite_url = '/'.join([path, item1])
print(sqlite_url)
response = requests.get(sqlite_url, headers=head, verify=False)
un_path = "repotest.sqlite.bz2"
with open(un_path, 'wb') as code:
code.write(response.content)
bz2file = bz2.BZ2File(un_path)
data = bz2file.read()
newfilepath = "repotest.sqlite"
open(newfilepath, 'wb').write(data)
return newfilepath
elif item1.endswith("primary.sqlite.xz"):
sqlite_url = '/'.join([path, item1])
print(sqlite_url)
response = requests.get(sqlite_url, headers=head, verify=False)
un_path = "repotest.sqlite.xz"
with open(un_path, 'wb') as code:
code.write(response.content)
with lzma.open(un_path, 'rb') as input:
with open("repotest.sqlite", 'wb') as output:
shutil.copyfileobj(input, output)
return "repotest.sqlite"
elif item1.endswith("primary.sqlite.gz"):
sqlite_url = '/'.join([path, item1])
response = requests.get(sqlite_url, headers=head, verify=False)
un_path = "repotest.sqlite.gz"
with open(un_path, 'wb') as code:
code.write(response.content)
with gzip.open(un_path, 'rb') as input:
with open("repotest.sqlite", 'wb') as output:
shutil.copyfileobj(input, output)
return "repotest.sqlite"
print("获取数据库文件失败,请检查!")
return None
except Exception as e:
print("发生异常:", e)
return None
# ---------------------
# 获取包信息
# ---------------------
def get_package_info_from_db1(baseurl):
db_path = Getsqlite_primary(baseurl)
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT pkgkey, name, arch, version, release, rpm_sourcerpm FROM packages")
result = cursor.fetchall()
conn.close()
pkg_info = {}
for pkgkey, name, arch, version, release, rpm_sourcerpm in result:
pkg_info[pkgkey] = {
"pkgkey": pkgkey,
"name": name,
"arch": arch,
"version": version,
"release": release,
"source": rpm_sourcerpm,
}
return pkg_info
def extract_srpm_nvr(pkg_info):
return set(info['source'].replace('.src.rpm', '').replace('.nosrc.rpm', '') for info in pkg_info.values() if info['source'])
def extract_binary_nvr(pkg_info):
return set(f"{info['name']}-{info['version']}-{info['release']}.{info['arch']}" for info in pkg_info.values())
def extract_srpm_names_from_nvr_list(nvr_list):
srpm_names = set()
for nvr in nvr_list:
parts = nvr.rsplit('-', 2)
if len(parts) == 3:
srpm_names.add(parts[0])
return sorted(srpm_names)
def analyze_binary_distribution_all_repos(all_pkg_info_by_repo):
srpm_versions = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
for repo_name, pkg_info in all_pkg_info_by_repo.items():
for info in pkg_info.values():
source_rpm = info['source']
binary_name = info['name']
try:
srpm_base = source_rpm.replace('.src.rpm', '').replace('.nosrc.rpm', '')
srpm_name, srpm_version, srpm_release = srpm_base.rsplit('-', 2)
srpm_fullver = f"{srpm_version}-{srpm_release}"
except Exception:
continue
srpm_versions[srpm_name][repo_name][srpm_fullver].add(binary_name)
srpm_change_flags = {}
srpm_all_binaries = {}
for srpm_name, repo_versions in srpm_versions.items():
all_binary_counts = []
all_binaries = set()
for version_map in repo_versions.values():
for binaries in version_map.values():
all_binary_counts.append(len(binaries))
all_binaries.update(binaries)
srpm_change_flags[srpm_name] = len(set(all_binary_counts)) > 1
srpm_all_binaries[srpm_name] = all_binaries
records = []
for srpm_name, repo_versions in srpm_versions.items():
changed = srpm_change_flags[srpm_name]
all_binaries = srpm_all_binaries[srpm_name]
for repo_name, versions in repo_versions.items():
pkg_info_map = all_pkg_info_by_repo[repo_name]
for version, binaries in versions.items():
added = sorted(binaries - all_binaries)
removed = sorted(all_binaries - binaries)
binary_nvrs = sorted([
f"{info['name']}-{info['version']}-{info['release']}.{info['arch']}"
for info in pkg_info_map.values()
if info["name"] in binaries and
info["source"].replace('.src.rpm', '').replace('.nosrc.rpm', '').endswith(version)
])
records.append({
"Repo": repo_name,
"SRPM Name": srpm_name,
"SRPM Version": version,
"Binary Count": len(binaries),
"Binary Package List": ' '.join(sorted(binaries)),
"Binary NVR List": ' '.join(binary_nvrs),
"Binary Count Changed": "Yes" if changed else "No",
"Added Binaries": ' '.join(added),
"Removed Binaries": ' '.join(removed),
})
df = pd.DataFrame(records)
df.sort_values(by=["SRPM Name", "SRPM Version", "Repo"], inplace=True)
return df
def write_test_comparison_sheet(wb, added_srpms, removed_srpms, added_bins, removed_bins):
sheet = wb.create_sheet("测试仓库对比")
sheet.append(["新增 SRPM仅测试仓库对比", "新增 Binary NVR仅测试仓库对比"])
for i in range(max(len(added_srpms), len(added_bins))):
sheet.append([added_srpms[i] if i < len(added_srpms) else "", added_bins[i] if i < len(added_bins) else ""])
sheet.append([])
sheet.append(["减少 SRPM仅测试仓库对比", "减少 Binary NVR仅测试仓库对比"])
for i in range(max(len(removed_srpms), len(removed_bins))):
sheet.append([removed_srpms[i] if i < len(removed_srpms) else "", removed_bins[i] if i < len(removed_bins) else ""])
def write_filtered_analysis_sheet(analysis_df, target_srpms, worksheet):
filtered_df = analysis_df[analysis_df["SRPM Name"].isin(target_srpms)]
worksheet.append(list(filtered_df.columns))
for _, row in filtered_df.iterrows():
worksheet.append(list(row.values))
def write_analysis_to_excel(analysis_df, repo_name, worksheet, is_first=False):
if is_first:
worksheet.append(["Repo"] + list(analysis_df.columns))
for _, row in analysis_df.iterrows():
worksheet.append([repo_name] + list(row.values))
def load_product_config():
config = configparser.ConfigParser()
config.read('config.ini', encoding='utf-8')
all_products = {}
for section in config.sections():
if not config.has_option(section, 'baseurls'):
continue
baseurls_raw = config.get(section, 'baseurls', fallback='')
baseurl_lines = [line.strip() for line in baseurls_raw.splitlines() if line.strip()]
baseurl_dict = {}
for line in baseurl_lines:
if '=' in line:
name, url = line.split('=', 1)
baseurl_dict[name.strip()] = url.strip()
filename = config.get(section, 'filename', fallback=section)
updates_test = config.get(section, 'updates_test', fallback=None)
all_products[section] = {
"baseurls": baseurl_dict,
"filename": filename,
"updates_test": updates_test
}
return all_products
def compare_updates_and_test(updates_url, test_url):
new_info = get_package_info_from_db1(updates_url)
test_info = get_package_info_from_db1(test_url)
new_srpms = extract_srpm_nvr(new_info)
test_srpms = extract_srpm_nvr(test_info)
new_bins = extract_binary_nvr(new_info)
test_bins = extract_binary_nvr(test_info)
added_srpms = sorted(test_srpms - new_srpms)
removed_srpms = sorted(new_srpms - test_srpms)
added_bins = sorted(test_bins - new_bins)
removed_bins = sorted(new_bins - test_bins)
focus_srpms = extract_srpm_names_from_nvr_list(added_srpms + removed_srpms)
return added_srpms, removed_srpms, added_bins, removed_bins, focus_srpms
def run_for_product(product_name, baseurl_map, filename_prefix, updates_test_url, show_raw=False):
print(f"\n🔍 正在分析产品:{product_name}")
output_dir = os.path.join("output", product_name)
os.makedirs(output_dir, exist_ok=True)
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M")
excel_path = os.path.join(output_dir, f"{filename_prefix}-分析报告-{timestamp}.xlsx")
pkglist_path = os.path.join(output_dir, f"Pkglist-{product_name}")
wb = openpyxl.Workbook()
if show_raw:
raw_sheet = wb.create_sheet("仓库原始数据")
analysis_sheet = wb.create_sheet("仓库所有SRPM分析")
focus_sheet_external = wb.create_sheet("外网仓库本周重点分析的SRPM")
focus_sheet_test = wb.create_sheet("测试仓库本周重点分析的SRPM")
wb.remove(wb.active)
all_pkg_info_by_repo = {}
if show_raw:
is_first_raw = True
for repo_name, baseurl in baseurl_map.items():
info = get_package_info_from_db1(baseurl)
all_pkg_info_by_repo[repo_name] = info
write_Excel(info, repo_name, raw_sheet, is_first=is_first_raw)
is_first_raw = False
else:
for repo_name, baseurl in baseurl_map.items():
all_pkg_info_by_repo[repo_name] = get_package_info_from_db1(baseurl)
analysis_df = analyze_binary_distribution_all_repos(all_pkg_info_by_repo)
write_analysis_to_excel(analysis_df, "Merged", analysis_sheet, is_first=True)
updates_url = baseurl_map.get("updates")
base_url = baseurl_map.get("base")
if updates_url and updates_test_url:
added_srpms, removed_srpms, added_bins, removed_bins, focus_srpms = compare_updates_and_test(updates_url, updates_test_url)
write_test_comparison_sheet(wb, added_srpms, removed_srpms, added_bins, removed_bins)
with open(pkglist_path, 'w') as f:
for nvr in added_bins:
f.write(nvr + '\n')
ext_info = {
'base': get_package_info_from_db1(base_url),
'updates': get_package_info_from_db1(updates_url)
}
ext_df = analyze_binary_distribution_all_repos(ext_info)
write_filtered_analysis_sheet(ext_df, focus_srpms, focus_sheet_external)
test_info = {
'base': get_package_info_from_db1(base_url),
'updates_test': get_package_info_from_db1(updates_test_url)
}
test_df = analyze_binary_distribution_all_repos(test_info)
write_filtered_analysis_sheet(test_df, focus_srpms, focus_sheet_test)
wb.save(excel_path)
print(f"{product_name} 分析完成,输出文件:{excel_path}")
# ✅ 生成安装卸载测试脚本(不需要传参)
install_test_script_path = os.path.join(output_dir, "install_remove_test.sh")
with open(install_test_script_path, "w") as f:
f.write(f"""#!/bin/bash
PACKAGE_LIST=Pkglist-{product_name}
OUTPUT_FILE={product_name}_install_results.csv
LOG_FILE={product_name}_install_test.log
echo \"Package,Install Status,Remove Status,Error\" > "$OUTPUT_FILE"
echo \"===== 安装测试日志开始 =====\" > "$LOG_FILE"
while IFS= read -r package
do
if [ -z "$package" ]; then
continue
fi
echo "正在安装: $package" | tee -a "$LOG_FILE"
install_output=$(yum --setopt=timeout=300 --setopt=retries=10 install -y "$package" 2>&1)
install_status=$?
if [ $install_status -eq 0 ]; then
install_result=成功
else
install_result=失败
fi
echo "$install_output" >> "$LOG_FILE"
echo "正在卸载: $package" | tee -a "$LOG_FILE"
remove_output=$(yum remove -y "$package" 2>&1)
remove_status=$?
if [ $remove_status -eq 0 ]; then
remove_result=成功
else
remove_result=失败
fi
echo "$remove_output" >> "$LOG_FILE"
if [ "$install_result" = "失败" ]; then
error_msg="$install_output"
elif [ "$remove_result" = "失败" ]; then
error_msg="$remove_output"
else
error_msg=""
fi
echo "$package,$install_result,$remove_result,\"$error_msg\"" >> "$OUTPUT_FILE"
echo "--------------------------------" >> "$LOG_FILE"
done < "$PACKAGE_LIST"
echo \"===== 安装测试日志结束 =====\" >> "$LOG_FILE"
echo \"测试完成,结果保存到 $OUTPUT_FILE过程日志保存到 $LOG_FILE\"
""")
os.chmod(install_test_script_path, 0o755)
# 🔧 生成 SRPM 升降级测试脚本
success = gen_updown_test([excel_path], filter_choice="All", sheet_name="测试仓库本周重点分析的SRPM")
if success:
for fname in os.listdir("."):
if fname.startswith("test_upgrade_downgrade_") and fname.endswith(".sh"):
shutil.move(fname, os.path.join(output_dir, fname))
elif fname.startswith("test_results_") or fname.startswith("test_process_"):
shutil.move(fname, os.path.join(output_dir, fname))
# # ✅ 汇总测试结果(安装卸载 + 升级降级)
# generate_test_summary_excel(output_dir, product_name)
def main():
products = load_product_config()
for name, conf in products.items():
run_for_product(
product_name=name,
baseurl_map=conf['baseurls'],
filename_prefix=conf['filename'],
updates_test_url=conf.get('updates_test'),
show_raw=False
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,319 @@
from collections import defaultdict
import bz2
import lzma
import shutil
import sqlite3
import requests
from lxml import etree
import configparser
import datetime
import pandas as pd
import openpyxl
import gzip
from openpyxl.styles import Font, PatternFill
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ---------------------
# 下载 primary.sqlite 并解压
# ---------------------
def Getsqlite_primary(baseurl):
"""
从repodata中获取数据库压缩文件下载并解压
url 格式 例如 basrutl=https://update.cs2c.com.cn/NS/V10/8U6/os/adv/lic/AppStream/x86_64/os/
"""
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
path = f"{baseurl}repodata/"
try:
response = requests.get(path, headers=head, verify=False)
html = response.text
html = etree.HTML(html)
a_links = html.xpath("//a")
for item in a_links:
item1 = item.get('href')
if item1.endswith("primary.sqlite.bz2"):
sqlite_url = '/'.join([path, item1])
print(sqlite_url)
response = requests.get(sqlite_url, headers=head, verify=False)
un_path = "repotest.sqlite.bz2"
with open(un_path, 'wb') as code:
code.write(response.content)
bz2file = bz2.BZ2File(un_path)
data = bz2file.read()
newfilepath = "repotest.sqlite"
open(newfilepath, 'wb').write(data)
return newfilepath
elif item1.endswith("primary.sqlite.xz"):
sqlite_url = '/'.join([path, item1])
response = requests.get(sqlite_url, headers=head, verify=False)
un_path = "repotest.sqlite.xz"
with open(un_path, 'wb') as code:
code.write(response.content)
with lzma.open(un_path, 'rb') as input:
with open("repotest.sqlite", 'wb') as output:
shutil.copyfileobj(input, output)
return "repotest.sqlite"
elif item1.endswith("primary.sqlite.gz"):
sqlite_url = '/'.join([path, item1])
print(sqlite_url)
response = requests.get(sqlite_url, headers=head, verify=False)
un_path = "repotest.sqlite.gz"
with open(un_path, 'wb') as code:
code.write(response.content)
with gzip.open(un_path, 'rb') as input:
with open("repotest.sqlite", 'wb') as output:
shutil.copyfileobj(input, output)
return "repotest.sqlite"
print("获取数据库文件失败,请检查!")
return None
except Exception as e:
print("发生异常:", e)
return None
# ---------------------
# 获取包信息
# ---------------------
def get_package_info_from_db1(baseurl):
db_path = Getsqlite_primary(baseurl)
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT pkgkey, name, arch, version, release, rpm_sourcerpm FROM packages")
result = cursor.fetchall()
conn.close()
pkg_info = {}
for pkgkey, name, arch, version, release, rpm_sourcerpm in result:
pkg_info[pkgkey] = {
"pkgkey": pkgkey,
"name": name,
"arch": arch,
"version": version,
"release": release,
"source": rpm_sourcerpm,
}
return pkg_info
# ---------------------
# 提取 SRPM 名称/NVR
# ---------------------
def extract_srpm_nvr(pkg_info):
return set(info['source'].replace('.src.rpm', '').replace('.nosrc.rpm', '') for info in pkg_info.values() if info['source'])
def extract_binary_nvr(pkg_info):
return set(f"{info['name']}-{info['version']}-{info['release']}.{info['arch']}" for info in pkg_info.values())
def extract_srpm_names_from_nvr_list(nvr_list):
srpm_names = set()
for nvr in nvr_list:
parts = nvr.rsplit('-', 2)
if len(parts) == 3:
srpm_names.add(parts[0])
return sorted(srpm_names)
# ---------------------
# 差异分析(多仓库)
# ---------------------
def analyze_binary_distribution_all_repos(all_pkg_info_by_repo):
srpm_versions = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
for repo_name, pkg_info in all_pkg_info_by_repo.items():
for info in pkg_info.values():
source_rpm = info['source']
binary_name = info['name']
try:
srpm_base = source_rpm.replace('.src.rpm', '').replace('.nosrc.rpm', '')
srpm_name, srpm_version, srpm_release = srpm_base.rsplit('-', 2)
srpm_fullver = f"{srpm_version}-{srpm_release}"
except Exception:
continue
srpm_versions[srpm_name][repo_name][srpm_fullver].add(binary_name)
srpm_change_flags = {}
srpm_all_binaries = {}
for srpm_name, repo_versions in srpm_versions.items():
all_binary_counts = []
all_binaries = set()
for version_map in repo_versions.values():
for binaries in version_map.values():
all_binary_counts.append(len(binaries))
all_binaries.update(binaries)
srpm_change_flags[srpm_name] = len(set(all_binary_counts)) > 1
srpm_all_binaries[srpm_name] = all_binaries
records = []
for srpm_name, repo_versions in srpm_versions.items():
changed = srpm_change_flags[srpm_name]
all_binaries = srpm_all_binaries[srpm_name]
for repo_name, versions in repo_versions.items():
pkg_info_map = all_pkg_info_by_repo[repo_name]
for version, binaries in versions.items():
added = sorted(binaries - all_binaries)
removed = sorted(all_binaries - binaries)
binary_nvrs = sorted([
f"{info['name']}-{info['version']}-{info['release']}.{info['arch']}"
for info in pkg_info_map.values()
if info["name"] in binaries and
info["source"].replace('.src.rpm', '').replace('.nosrc.rpm', '').endswith(version)
])
records.append({
"Repo": repo_name,
"SRPM Name": srpm_name,
"SRPM Version": version,
"Binary Count": len(binaries),
"Binary Package List": ' '.join(sorted(binaries)),
"Binary NVR List": ' '.join(binary_nvrs),
"Binary Count Changed": "Yes" if changed else "No",
"Added Binaries": ' '.join(added),
"Removed Binaries": ' '.join(removed),
})
df = pd.DataFrame(records)
df.sort_values(by=["SRPM Name", "SRPM Version", "Repo"], inplace=True)
return df
# ---------------------
# 对比测试仓库并返回重点 SRPM
# ---------------------
def compare_updates_and_test(updates_url, test_url):
new_info = get_package_info_from_db1(updates_url)
test_info = get_package_info_from_db1(test_url)
new_srpms = extract_srpm_nvr(new_info)
test_srpms = extract_srpm_nvr(test_info)
new_bins = extract_binary_nvr(new_info)
test_bins = extract_binary_nvr(test_info)
added_srpms = sorted(test_srpms - new_srpms)
removed_srpms = sorted(new_srpms - test_srpms)
added_bins = sorted(test_bins - new_bins)
removed_bins = sorted(new_bins - test_bins)
focus_srpms = extract_srpm_names_from_nvr_list(added_srpms + removed_srpms)
return added_srpms, removed_srpms, added_bins, removed_bins, focus_srpms
def write_test_comparison_sheet(wb, added_srpms, removed_srpms, added_bins, removed_bins):
sheet = wb.create_sheet("测试仓库对比")
sheet.append(["新增 SRPM仅测试仓库对比", "新增 Binary NVR仅测试仓库对比"])
for i in range(max(len(added_srpms), len(added_bins))):
sheet.append([added_srpms[i] if i < len(added_srpms) else "", added_bins[i] if i < len(added_bins) else ""])
sheet.append([])
sheet.append(["减少 SRPM仅测试仓库对比", "减少 Binary NVR仅测试仓库对比"])
for i in range(max(len(removed_srpms), len(removed_bins))):
sheet.append([removed_srpms[i] if i < len(removed_srpms) else "", removed_bins[i] if i < len(removed_bins) else ""])
# ---------------------
# 写入分析表
# ---------------------
def write_filtered_analysis_sheet(analysis_df, target_srpms, worksheet):
filtered_df = analysis_df[analysis_df["SRPM Name"].isin(target_srpms)]
worksheet.append(list(filtered_df.columns))
for _, row in filtered_df.iterrows():
worksheet.append(list(row.values))
def write_analysis_to_excel(analysis_df, repo_name, worksheet, is_first=False):
if is_first:
worksheet.append(["Repo"] + list(analysis_df.columns))
for _, row in analysis_df.iterrows():
worksheet.append([repo_name] + list(row.values))
# ---------------------
# 配置读取
# ---------------------
def load_product_config():
config = configparser.ConfigParser()
config.read('config.ini', encoding='utf-8')
all_products = {}
for section in config.sections():
if not config.has_option(section, 'baseurls'):
continue
baseurls_raw = config.get(section, 'baseurls', fallback='')
baseurl_lines = [line.strip() for line in baseurls_raw.splitlines() if line.strip()]
baseurl_dict = {}
for line in baseurl_lines:
if '=' in line:
name, url = line.split('=', 1)
baseurl_dict[name.strip()] = url.strip()
filename = config.get(section, 'filename', fallback=section)
updates_test = config.get(section, 'updates_test', fallback=None)
all_products[section] = {
"baseurls": baseurl_dict,
"filename": filename,
"updates_test": updates_test
}
return all_products
# ---------------------
# 主执行逻辑
# ---------------------
def run_for_product(product_name, baseurl_map, filename_prefix, updates_test_url, show_raw=False):
print(f"\n🔍 正在分析产品:{product_name}")
wb = openpyxl.Workbook()
if show_raw:
raw_sheet = wb.create_sheet("仓库原始数据")
analysis_sheet = wb.create_sheet("仓库所有SRPM分析")
focus_sheet_external = wb.create_sheet("外网仓库本周重点分析的SRPM")
focus_sheet_test = wb.create_sheet("测试仓库本周重点分析的SRPM")
wb.remove(wb.active)
all_pkg_info_by_repo = {}
if show_raw:
is_first_raw = True
for repo_name, baseurl in baseurl_map.items():
info = get_package_info_from_db1(baseurl)
all_pkg_info_by_repo[repo_name] = info
write_Excel(info, repo_name, raw_sheet, is_first=is_first_raw)
is_first_raw = False
else:
for repo_name, baseurl in baseurl_map.items():
all_pkg_info_by_repo[repo_name] = get_package_info_from_db1(baseurl)
analysis_df = analyze_binary_distribution_all_repos(all_pkg_info_by_repo)
write_analysis_to_excel(analysis_df, "Merged", analysis_sheet, is_first=True)
updates_url = baseurl_map.get("updates")
base_url = baseurl_map.get("base")
if updates_url and updates_test_url:
added_srpms, removed_srpms, added_bins, removed_bins, focus_srpms = compare_updates_and_test(updates_url, updates_test_url)
write_test_comparison_sheet(wb, added_srpms, removed_srpms, added_bins, removed_bins)
# 外网重点分析base + updates
ext_info = {
'base': get_package_info_from_db1(base_url),
'updates': get_package_info_from_db1(updates_url)
}
ext_df = analyze_binary_distribution_all_repos(ext_info)
write_filtered_analysis_sheet(ext_df, focus_srpms, focus_sheet_external)
# 测试重点分析base + updates_test
test_info = {
'base': get_package_info_from_db1(base_url),
'updates_test': get_package_info_from_db1(updates_test_url)
}
test_df = analyze_binary_distribution_all_repos(test_info)
write_filtered_analysis_sheet(test_df, focus_srpms, focus_sheet_test)
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M")
output_file = f"{filename_prefix}-{timestamp}.xlsx"
wb.save(output_file)
print(f"{product_name} 分析完成,输出文件:{output_file}")
# ---------------------
# 主函数
# ---------------------
def main():
products = load_product_config()
for name, conf in products.items():
run_for_product(
product_name=name,
baseurl_map=conf['baseurls'],
filename_prefix=conf['filename'],
updates_test_url=conf.get('updates_test'),
show_raw=False
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,170 @@
[V10SP4-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/
filename = V10SP3-2403-x86分析报告
[V10SP3-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/x86_64/
filename = V10SP3-x86分析报告
[V10SP2-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/x86_64/
filename = V10SP2-x86分析报告
[V10SP1-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/x86_64/
filename = V10SP1-x86分析报告
[HPC-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/x86_64/
filename = HPC-x86分析报告
[HOST-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/x86_64/
filename = HOST-x86分析报告
[yundizuoV10-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/x86_64/
filename = yundizuoV10-x86分析报告
[V10-ZJ-x86]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/x86_64/
updates=https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/x86_64/
filename = V10-ZJ-x86分析报告
[2309a]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/2309A/os/adv/lic/updates/aarch64/
filename = 2309A分析报告
[2309b]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/2309B/os/adv/lic/updates/aarch64/
filename = 2309B分析报告
[V10SP4-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/aarch64/
filename = V10SP3-2403-aarch64分析报告
[V10SP3-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/aarch64/
filename = V10SP3-aarch64分析报告
[V10SP2-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/aarch64/
filename = V10SP2-aarch64分析报告
[V10SP1-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/aarch64/
filename = V10SP1-aarch64分析报告
[HPC-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/aarch64/
filename = HPC-aarch64分析报告
[HOST-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/aarch64/
filename = HOST-aarch64分析报告
[yundizuoV10-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/aarch64/
filename = yundizuoV10-aarch64分析报告
[V10-ZJ-aarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/aarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/aarch64/
filename = V10-ZJ-aarch64分析报告
[V10SP4-loongarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/loongarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/
filename = V10SP3-2403-loongarch64分析报告
[V10SP3-loongarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/loongarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/loongarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/loongarch64/
filename = V10SP3-loongarch64分析报告
[V10SP1-loongarch64]
baseurls =
base=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/loongarch64/
updates=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/loongarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/loongarch64/
filename = V10SP1-loongarch64分析报告
[V7-x86]
baseurls =
base= https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/base/x86_64/
updates= https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/x86_64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V7/V7Update9/os/adv/lic/updates/x86_64/
filename = V7-x86分析报告
[V7-arm]
baseurls =
base= https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/base/aarch64/
updates= https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/aarch64/
updates_test = https://update.cs2c.com.cn/private_test/repo/V7/V7Update9/os/adv/lic/updates/aarch64/
filename = V7-arm分析报告

View File

@@ -0,0 +1,137 @@
# test_srpm_up_down.py
import os
from openpyxl import load_workbook
from rpm_vercmp import vercmp
from functools import cmp_to_key
import re
def process_excel_to_dict(file_path, filter_choice, sheet_name):
wb = load_workbook(filename=file_path)
sheet = wb[sheet_name]
headers = [cell.value for cell in sheet[1]]
try:
name_idx = headers.index("SRPM Name")
version_idx = headers.index("SRPM Version")
binary_idx = headers.index("Binary NVR List")
changed_idx = headers.index("Binary Count Changed")
except ValueError as e:
print(f"Error: 缺少必要的列 - {e}")
return None
result_dict = {}
for row in sheet.iter_rows(min_row=2, values_only=True):
if filter_choice == "Yes" and row[changed_idx] != "Yes":
continue
if filter_choice == "No" and row[changed_idx] != "No":
continue
name = row[name_idx]
version = row[version_idx]
binary_pkgs = row[binary_idx]
if name and version:
key = f"{name}-{version}"
result_dict[key] = binary_pkgs
return result_dict
def process_single_file(file_path, filter_choice, sheet_name):
result = process_excel_to_dict(file_path, filter_choice, sheet_name)
if not result:
print(f"警告: 文件 {file_path} 处理失败,跳过")
return
srpm_groups = {}
for key, value in result.items():
parts = key.split('-')
name = '-'.join(parts[:-2])
srpm_groups.setdefault(name, []).append((key, value))
for name in srpm_groups:
srpm_groups[name] = sorted(srpm_groups[name], key=cmp_to_key(lambda a, b: vercmp(a[0], b[0])))
base_name = re.sub(r'[^\x00-\x7F]+', '', os.path.basename(file_path)).split('.')[0]
output_dir = os.path.dirname(file_path)
script_name = os.path.join(output_dir, f"test_upgrade_downgrade_{base_name}.sh")
results_log = f"test_results_{base_name}.log"
process_log = f"test_process_{base_name}.log"
try:
with open(script_name, 'w') as f:
f.write("#!/bin/bash\n\n")
f.write(f"# Auto-generated from {base_name}\n")
f.write(f"rm -f {results_log} {process_log}\n")
f.write(f"touch {process_log}\n")
f.write(f"exec > >(tee -a {process_log}) 2>&1\n")
f.write("echo 'Logging started at $(date)'\n")
f.write("echo '========================================'\n\n")
for name, versions in srpm_groups.items():
if len(versions) < 2:
continue
f.write(f"echo '\n=== Testing SRPM: {name} ==='\n")
if sheet_name == "测试仓库本周重点分析的SRPM":
highest_idx = len(versions) - 1
highest_ver = versions[highest_idx][0]
highest_pkgs = ' '.join(versions[highest_idx][1].split())
for i in range(highest_idx):
from_ver = versions[i][0]
from_pkgs = ' '.join(versions[i][1].split())
f.write(f"echo 'Installing base version: {from_ver}'\n")
f.write(f"if ! yum install -y {from_pkgs}; then\n")
f.write(f" echo 'BASE INSTALL FAILED: {from_ver}' >> {results_log}\n")
f.write(f"else\n")
f.write(f" echo 'BASE INSTALL SUCCESS: {from_ver}' >> {results_log}\n")
f.write(f" echo 'Testing upgrade: {from_ver} -> {highest_ver}'\n")
f.write(f" if yum upgrade -y {highest_pkgs}; then\n")
f.write(f" echo 'UPGRADE SUCCESS' >> {results_log}\n")
f.write(f" else\n")
f.write(f" echo 'UPGRADE FAILED' >> {results_log}\n")
f.write(f" fi\n")
f.write(f" echo 'Testing downgrade: {highest_ver} -> {from_ver}'\n")
f.write(f" if yum downgrade -y {from_pkgs}; then\n")
f.write(f" echo 'DOWNGRADE SUCCESS' >> {results_log}\n")
f.write(f" else\n")
f.write(f" echo 'DOWNGRADE FAILED' >> {results_log}\n")
f.write(f" fi\n")
f.write(f"fi\n")
f.write(f"echo '=== TEST END ===' >> {results_log}\n\n")
else:
for i in range(len(versions)):
for j in range(i+1, len(versions)):
from_ver = versions[i][0]
to_ver = versions[j][0]
from_pkgs = ' '.join(versions[i][1].split())
to_pkgs = ' '.join(versions[j][1].split())
f.write(f"echo 'Installing base version: {from_ver}'\n")
f.write(f"if ! yum install -y {from_pkgs}; then\n")
f.write(f" echo 'BASE INSTALL FAILED: {from_ver}' >> {results_log}\n")
f.write(f"else\n")
f.write(f" echo 'Testing upgrade: {from_ver} -> {to_ver}'\n")
f.write(f" if yum upgrade -y {to_pkgs}; then\n")
f.write(f" echo 'UPGRADE SUCCESS' >> {results_log}\n")
f.write(f" else\n")
f.write(f" echo 'UPGRADE FAILED' >> {results_log}\n")
f.write(f" fi\n")
f.write(f" echo 'Testing downgrade: {to_ver} -> {from_ver}'\n")
f.write(f" if yum downgrade -y {from_pkgs}; then\n")
f.write(f" echo 'DOWNGRADE SUCCESS' >> {results_log}\n")
f.write(f" else\n")
f.write(f" echo 'DOWNGRADE FAILED' >> {results_log}\n")
f.write(f" fi\n")
f.write(f"fi\n")
f.write(f"echo '=== TEST END ===' >> {results_log}\n\n")
print(f"✅ 成功生成测试脚本: {script_name}")
except Exception as e:
print(f"❌ 生成脚本失败: {e}")
def gen_updown_test(excel_files, filter_choice="All", sheet_name="测试仓库本周重点分析的SRPM"):
if not excel_files:
print("错误: 未提供任何Excel文件")
return False
for file_path in excel_files:
if not os.path.exists(file_path):
print(f"警告: 文件不存在 {file_path}")
continue
print(f"\n处理文件: {os.path.basename(file_path)}")
process_single_file(file_path, filter_choice, sheet_name)
return True

View File

@@ -0,0 +1,76 @@
#!/bin/bash
# -------------------------
# 读取命令行传入的产品名和架构
# -------------------------
if [ $# -ne 2 ]; then
echo "用法: $0 <PRODUCT_NAME> <ARCH>"
echo "示例: $0 V10 x86_64"
exit 1
fi
PRODUCT_NAME="$1"
ARCH="$2"
# 软件包列表文件
PACKAGE_LIST="Pkglist"
# 自动生成输出文件名
OUTPUT_FILE="测试项2-${PRODUCT_NAME}_${ARCH}_install_results-.csv"
LOG_FILE="测试项2-${PRODUCT_NAME}_${ARCH}_install_test-.log"
# 创建或清空输出文件
echo "Package,Install Status,Remove Status,Error" > "$OUTPUT_FILE"
echo "===== 安装测试日志开始 =====" > "$LOG_FILE"
# 逐行读取软件包名称并安装
while IFS= read -r package
do
if [ -z "$package" ]; then
continue
fi
echo "正在安装: $package" | tee -a "$LOG_FILE"
# 真正安装软件包
install_output=$(yum --setopt=timeout=300 --setopt=retries=10 install -y "$package" 2>&1)
install_status=$?
if [ $install_status -eq 0 ]; then
install_result="成功"
else
install_result="失败"
fi
echo "$install_output" >> "$LOG_FILE"
# 如果安装成功,执行卸载
echo "正在卸载: $package" | tee -a "$LOG_FILE"
remove_output=$(yum remove -y "$package" 2>&1)
remove_status=$?
if [ $remove_status -eq 0 ]; then
remove_result="成功"
else
remove_result="失败"
fi
echo "$remove_output" >> "$LOG_FILE"
# 写入 CSV错误信息用双引号包裹防止格式错乱
if [ "$install_result" = "失败" ]; then
error_msg="$install_output"
elif [ "$remove_result" = "失败" ]; then
error_msg="$remove_output"
else
error_msg=""
fi
echo "$package,$install_result,$remove_result,\"$error_msg\"" >> "$OUTPUT_FILE"
echo "--------------------------------" >> "$LOG_FILE"
done < "$PACKAGE_LIST"
echo "===== 安装测试日志结束 =====" >> "$LOG_FILE"
echo "测试完成,结果保存到 $OUTPUT_FILE,过程日志保存到 $LOG_FILE"

View File

@@ -0,0 +1,36 @@
# 源码包多版本升降级测试
## 前置条件
- 需要基于丁瑶脚本生成的表格文件
- 确保执行环境已安装所需依赖库
## 使用步骤
1. 执行测试脚本:
```bash
python3 test_srpm_up_down_0512.py
```
2. 根据提示进行选择:
- **第一步** 输入文件路径:
- 1) 多个表格文件:输入文件夹绝对路径
- 2) 单个表格文件:输入文件绝对路径
- **第二步** 筛选条件(Binary Count Changed列)
- 1) 仅筛选"No":多版本但包个数一致
- 2) 仅筛选"Yes":多版本但包个数不一致
- 3) 不筛选:处理全部数据
- **第三步** 选择工作表:
- 1) 测试仓库本周重点分析的SRPM
- 生成测试顺序A→D, D→A, B→D, D→B, C→D, D→C
- 2) 仓库所有SRPM分析
- 生成所有版本排列组合的升降级测试
## 后续操作
- 执行完成后会生成测试脚本
- 将脚本传输到测试机执行:
```bash
bash 测试脚本
```
> **注意**
> 当前生成的测试脚本存在缺陷:
> 如果base install版本安装失败后续升降级测试需手动确认

View File

@@ -0,0 +1,232 @@
import os
import openpyxl
from openpyxl import load_workbook
from rpm_vercmp import vercmp
from functools import cmp_to_key
def process_excel_to_dict(file_path, filter_choice, sheet_name):
# 加载工作簿并选择指定工作表
wb = load_workbook(filename=file_path)
sheet = wb[sheet_name]
# 获取表头行,确定列索引
headers = [cell.value for cell in sheet[1]]
try:
name_idx = headers.index("SRPM Name")
version_idx = headers.index("SRPM Version")
binary_idx = headers.index("Binary NVR List")
changed_idx = headers.index("Binary Count Changed")
except ValueError as e:
print(f"Error: 缺少必要的列 - {e}")
return None
result_dict = {}
# 从第二行开始处理数据
for row in sheet.iter_rows(min_row=2, values_only=True):
if filter_choice == "Yes" and row[changed_idx] != "Yes":
continue
if filter_choice == "No" and row[changed_idx] != "No":
continue
name = row[name_idx]
version = row[version_idx]
binary_pkgs = row[binary_idx]
if name and version: # 确保关键字段不为空
key = f"{name}-{version}"
result_dict[key] = binary_pkgs
return result_dict
def process_single_file(file_path, filter_choice, sheet_name):
result = process_excel_to_dict(file_path, filter_choice, sheet_name)
# 按SRPM名称分组
srpm_groups = {}
for key, value in result.items():
parts = key.split('-')
name = '-'.join(parts[:-2])
if name not in srpm_groups:
srpm_groups[name] = []
srpm_groups[name].append((key, value))
# 在每个分组内按版本号排序
for name in srpm_groups:
srpm_groups[name] = sorted(srpm_groups[name],
key=cmp_to_key(lambda a,b: vercmp(a[0], b[0])))
if not result:
print(f"警告: 文件 {file_path} 处理失败,跳过")
return
# 从Excel文件名生成基础名(不含路径、扩展名和中文)
import re
base_name = re.sub(r'[^\x00-\x7F]+', '', os.path.basename(file_path)).split('.')[0]
# 生成测试脚本
script_name = f"test_upgrade_downgrade_{base_name}.sh"
results_log = f"test_results_{base_name}.log"
process_log = f"test_process_{base_name}.log"
try:
with open(script_name, 'w') as f:
f.write("#!/bin/bash\n\n")
f.write(f"# Auto-generated SRPM upgrade/downgrade test script from {base_name}\n")
f.write("# Tests all version combinations for each SRPM\n")
f.write(f"# Results will be saved to {results_log}\n")
f.write(f"# Process details will be saved to {process_log}\n\n")
f.write(f"rm -f {results_log} {process_log}\n")
f.write(f"touch {process_log} \n")
f.write(f"exec > >(tee -a {process_log}) 2>&1\n")
f.write("echo 'Logging started at $(date)'\n")
f.write("echo '========================================'\n\n")
for name, versions in srpm_groups.items():
if len(versions) < 2:
continue
f.write(f"echo '\n=== Testing SRPM: {name} ==='\n")
if sheet_name == "测试仓库本周重点分析的SRPM":
# 本周重点分析的特殊测试顺序:每个中间版本与最高版本测试
highest_idx = len(versions) - 1
highest_ver = versions[highest_idx][0]
highest_pkgs = ' '.join(versions[highest_idx][1].split())
for i in range(highest_idx):
from_ver = versions[i][0]
from_pkgs = ' '.join(versions[i][1].split())
# 1. 安装基本版本
f.write(f"echo 'Installing base version: {from_ver}'\n")
f.write(f"if ! yum install -y {from_pkgs}; then\n")
f.write(f" echo 'BASE INSTALL FAILED: {from_ver} - VERSION: {from_ver} upgrade/downgrade test requires manual handling' >> {results_log}\n")
f.write(f" echo '=== TEST END ===' >> {results_log}\n\n")
f.write(f"else\n")
f.write(f" echo 'BASE INSTALL SUCCESS: {from_ver}' >> {results_log}\n")
# 2. 升级到最高版本
f.write(f"echo 'Testing upgrade: {from_ver} -> {highest_ver}'\n")
f.write(f"if yum upgrade -y {highest_pkgs}; then\n")
f.write(f" echo 'UPGRADE SUCCESS: {name} from {from_ver} to {highest_ver}' >> {results_log}\n")
f.write(f"else\n")
f.write(f" echo 'UPGRADE FAILED: {name} from {from_ver} to {highest_ver}' >> {results_log}\n")
f.write(f"fi\n")
# 3. 降级回基本版本
f.write(f"echo 'Testing downgrade: {highest_ver} -> {from_ver}'\n")
f.write(f"if yum downgrade -y {from_pkgs}; then\n")
f.write(f" echo 'DOWNGRADE SUCCESS: {name} from {highest_ver} to {from_ver}' >> {results_log}\n")
f.write(f"else\n")
f.write(f" echo 'DOWNGRADE FAILED: {name} from {highest_ver} to {from_ver}' >> {results_log}\n")
f.write(f"fi\n")
f.write(f"echo '=== TEST END ===' >> {results_log}\n\n")
f.write(f"fi\n")
else:
# 原有测试逻辑:所有版本组合测试
for i in range(len(versions)):
for j in range(i+1, len(versions)):
from_ver = versions[i][0]
to_ver = versions[j][0]
from_pkgs = ' '.join(versions[i][1].split())
pkgs = ' '.join(versions[j][1].split())
# 1. 安装基本版本
f.write(f"echo 'Installing base version: {from_ver}'\n")
f.write(f"if ! yum install -y {from_pkgs}; then\n")
f.write(f" echo 'BASE INSTALL FAILED: {from_ver} - VERSION: {from_ver} upgrade/downgrade test requires manual handling' >> {results_log}\n")
f.write(f" echo '=== TEST END ===' >> {results_log}\n\n")
f.write(f"else\n")
f.write(f" echo 'BASE INSTALL SUCCESS: {from_ver}' >> {results_log}\n")
# 2. 升级测试
f.write(f"echo 'Testing upgrade: {from_ver} -> {to_ver}'\n")
f.write(f"if yum upgrade -y {pkgs}; then\n")
f.write(f" echo 'UPGRADE SUCCESS: {name} from {from_ver} to {to_ver}' >> {results_log}\n")
f.write(f"else\n")
f.write(f" echo 'UPGRADE FAILED: {name} from {from_ver} to {to_ver}' >> {results_log}\n")
f.write(f"fi\n")
# 3. 降级测试
f.write(f"echo 'Testing downgrade: {to_ver} -> {from_ver}'\n")
f.write(f"if yum downgrade -y {' '.join(versions[i][1].split())}; then\n")
f.write(f" echo 'DOWNGRADE SUCCESS: {name} from {to_ver} to {from_ver}' >> {results_log}\n")
f.write(f"else\n")
f.write(f" echo 'DOWNGRADE FAILED: {name} from {to_ver} to {from_ver}' >> {results_log}\n")
f.write(f"fi\n")
f.write(f"echo '=== TEST END ===' >> {results_log}\n\n")
f.write(f"fi\n")
print(f"成功生成测试脚本: {script_name}")
except Exception as e:
print(f"生成脚本失败: {e}")
if __name__ == "__main__":
print("请选择模式:")
print("1. 手动输入单个Excel文件路径")
print("2. 批量处理文件夹下所有Excel文件")
while True:
choice = input("请输入模式编号(1/2): ").strip()
if choice in ('1', '2'):
break
print("错误: 请输入1或2")
print("\n请选择Binary Count Changed筛选条件:")
print("1. 仅筛选Yes")
print("2. 仅筛选No")
print("3. 不筛选(全部处理)")
while True:
filter_choice = input("请输入筛选条件编号(1/2/3): ").strip()
if filter_choice == '1':
filter_choice = "Yes"
break
elif filter_choice == '2':
filter_choice = "No"
break
elif filter_choice == '3':
filter_choice = "All"
break
print("错误: 请输入1、2或3")
print("\n请选择工作表:")
print("1. 测试仓库本周重点分析的SRPM")
print("2. 仓库所有SRPM分析")
while True:
sheet_choice = input("请输入工作表编号(1/2): ").strip()
if sheet_choice == '1':
sheet_name = "测试仓库本周重点分析的SRPM"
break
elif sheet_choice == '2':
sheet_name = "仓库所有SRPM分析"
break
print("错误: 请输入1或2")
if choice == '1':
while True:
file_path = input("请输入Excel文件的绝对路径: ").strip()
if os.path.exists(file_path):
break
print(f"错误: 文件 '{file_path}' 不存在,请重新输入")
process_single_file(file_path, filter_choice, sheet_name)
else:
while True:
dir_path = input("请输入包含Excel文件的文件夹路径: ").strip()
if os.path.isdir(dir_path):
break
print(f"错误: 文件夹 '{dir_path}' 不存在,请重新输入")
for filename in os.listdir(dir_path):
if filename.endswith('.xlsx') or filename.endswith('.xls'):
file_path = os.path.join(dir_path, filename)
print(f"\n处理文件: {filename}")
process_single_file(file_path, filter_choice, sheet_name)

View File

@@ -0,0 +1,67 @@
# 测试项9 - 仓库中软件依赖检测脚本说明
## 脚本简介
本脚本用于测试指定产品的仓库中软件包的依赖异常使用dnf repoclosure工具检测仓库软件包的依赖。
脚本执行的终端环境架构需要和测试仓库的架构一致否则会有很多误报。所以按照架构aarch64,x86_64,loongarch64,mips64el有对应的测试脚本。
## 使用方法
1. 在x86_64的服务器终端执行
`python3 test_pkg_repoclosure_x86_64.py`
2. 在aarch64的服务器终端执行
`python3 test_pkg_repoclosure_aarch64.py`
3. 在loongarch64的服务器终端执行
`python3 test_pkg_repoclosure_loongarch64.py`
4. 在mips64el的服务器终端执行
`python3 test_pkg_repoclosure_mips64.py`
---
## 输入文件
测试脚本中的变量command_pairs 变量中包含当前测试产品版本对应的命令内容。该变量的每项由三部分组成:
1. 描述信息(如产品版本及架构)
2. 检查已发布仓库中软件包依赖情况的 Shell 命令
3. 检查本次更新仓库中软件包依赖情况的 Shell 命令
以 V10SP1.1 x86_64 为例,其 command_pairs 项如下:
```python
command_pairs = [
(
"V10SP1.1 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/x86_64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
)
]
```
---
## 输出文件
执行脚本执行时,会输出对应架构的执行结果,输出文件分别为:
- `package_comparison_aarch64.md`
- `package_comparison_x86_64.md`
- `package_comparison_loongarch64.md`
- `package_comparison_mips64el.md`
每个文件中:
- 结果1为已发布仓库中依赖报错软件包列表
- 结果2为测试仓库中依赖报错软件包列表
- 结果3结果2中列表比结果1中列表多的软件包的包名即测试仓库新增的报错包列表。结果为“无”说明本周更新的包没有新增依赖报错如果有包名则需要确认是否为误报及查看有报错的原因。
---
## 脚本流程说明
1. 执行dnf repoclosure 检查已发布的updates仓库是否有依赖报错将结果输出到结果1;
2. 执行dnf repoclosure 检查本周更新的updates仓库是否有依赖报错将结果输出到结果2;
3. 对比结果1和2输出结果3 = 结果2依赖报错列表 - 结果2依赖报错列表结果3是测试仓库新增的报错包列表如不是表示更新的包有依赖报错需要排查是否为误报
4. 将步骤1-3的结果输出到md文档中。
---
## 注意事项
- 需要在对应架构的服务器上执行对应的脚本,不同架构上也可以执行,但会有很多误报。
---

View File

@@ -0,0 +1,143 @@
import subprocess
import re
from datetime import datetime
# 功能:检查某个产品的仓库中软件包的依赖报错
# 输入依赖检测的bash命令
# 返回值: 依赖报错软件包列表
def get_package_list(command):
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
output = result.stdout
pattern = r'package: (\S+)'
packages = re.findall(pattern, output)
return set(packages)
except subprocess.CalledProcessError as e:
print(f"执行命令时出错: {e.stderr}")
return set()
# 功能将软件包的nvr处理后返回name字符串
# 输入字符串name-version-release
# 返回值: 字符串name
def extract_name(nvr_string):
# 匹配形如 name-version-release 格式,只提取 name 部分
match = re.match(r"^(.*)-[^-]+-[^-]+$", nvr_string)
return match.group(1) if match else nvr_string
# 定义命令对列表,每个元素为 (组名, 命令1, 命令2)
command_pairs = [
(
"NS7.9 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/aarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/aarch64/ --repofrompath updates,https://update.cs2c.com.cn/private_test/repo/V7/V7Update9/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP1.1 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/aarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP2 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/aarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/aarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 2403 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/aarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"HOSTOS aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"HOSTOS 2309 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"HOSTOS 2406 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2406/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/HOST/2406/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/HOST/2406/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 2309A aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/2309A/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 2309B aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/2309B/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10 HPC aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10 aarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/aarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/aarch64/ --check updates --all -n|grep \'package:\''
)
]
file_name = "package_comparison_aarch64.md"
with open(file_name, 'a') as f:
f.write(f"# 软件包依赖对比结果\n")
f.write(f"## 生成时间: {datetime.now()}\n\n")
for index, (group_name, command1, command2) in enumerate(command_pairs, 1):
package_list1 = get_package_list(command1)
package_list2 = get_package_list(command2)
more_packages = package_list2 - package_list1
less_packages = package_list1 - package_list2
names1 = [extract_name(nvr) for nvr in less_packages]
names2 = [extract_name(nvr) for nvr in more_packages]
more_names = set(names2) - set(names1)
with open(file_name, 'a') as f:
f.write(f"## {group_name} 对比结果\n\n")
f.write("### 已发布仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command1 + "\n")
f.write("```\n\n")
f.write("### 测试仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command2 + "\n")
f.write("```\n\n")
f.write("## 结果1已发布仓库中依赖报错软件包列表\n")
if less_packages:
for package in sorted(less_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果2测试仓库中依赖报错软件包列表\n")
if more_packages:
for package in sorted(more_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果3结果2中列表比结果1中列表多的软件包的包名即测试仓库新增的报错包列表\n")
if more_names:
for name in sorted(more_names):
f.write(f"- {name}\n")
else:
f.write("\n")
f.write("\n---\n\n")
print(f"{group_name} 对比结果已输出到 {file_name} 文件中。")

View File

@@ -0,0 +1,96 @@
import subprocess
import re
from datetime import datetime
# 功能:检查某个产品的仓库中软件包的依赖报错
# 输入依赖检测的bash命令
# 返回值: 依赖报错软件包列表
def get_package_list(command):
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
output = result.stdout
pattern = r'package: (\S+)'
packages = re.findall(pattern, output)
return set(packages)
except subprocess.CalledProcessError as e:
print(f"执行命令时出错: {e.stderr}")
return set()
# 功能将软件包的nvr处理后返回name字符串
# 输入字符串name-version-release
# 返回值: 字符串name
def extract_name(nvr_string):
# 匹配形如 name-version-release 格式,只提取 name 部分
match = re.match(r"^(.*)-[^-]+-[^-]+$", nvr_string)
return match.group(1) if match else nvr_string
# 定义命令对列表,每个元素为 (组名, 命令1, 命令2)
command_pairs = [
(
"V10SP1.1 loongarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/loongarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/loongarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/loongarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/loongarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 loongarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/loongarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/loongarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/loongarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/loongarch64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 2403 loongarch64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/loongarch64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/loongarch64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/ --check updates --all -n|grep \'package:\''
)
]
with open(file_name, 'a') as f:
f.write(f"# 软件包依赖对比结果\n")
f.write(f"## 生成时间: {datetime.now()}\n\n")
for index, (group_name, command1, command2) in enumerate(command_pairs, 1):
package_list1 = get_package_list(command1)
package_list2 = get_package_list(command2)
more_packages = package_list2 - package_list1
less_packages = package_list1 - package_list2
names1 = [extract_name(nvr) for nvr in less_packages]
names2 = [extract_name(nvr) for nvr in more_packages]
more_names = set(names2) - set(names1)
with open(file_name, 'a') as f:
f.write(f"## {group_name} 对比结果\n\n")
f.write("### 已发布仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command1 + "\n")
f.write("```\n\n")
f.write("### 测试仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command2 + "\n")
f.write("```\n\n")
f.write("## 结果1已发布仓库中依赖报错软件包列表\n")
if less_packages:
for package in sorted(less_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果2测试仓库中依赖报错软件包列表\n")
if more_packages:
for package in sorted(more_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果3结果2中列表比结果1中列表多的软件包的包名即测试仓库新增的报错包列表\n")
if more_names:
for name in sorted(more_names):
f.write(f"- {name}\n")
else:
f.write("\n")
f.write("\n---\n\n")
print(f"{group_name} 对比结果已输出到 {file_name} 文件中。")

View File

@@ -0,0 +1,89 @@
import subprocess
import re
from datetime import datetime
# 功能:检查某个产品的仓库中软件包的依赖报错
# 输入依赖检测的bash命令
# 返回值: 依赖报错软件包列表
def get_package_list(command):
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
output = result.stdout
pattern = r'package: (\S+)'
packages = re.findall(pattern, output)
return set(packages)
except subprocess.CalledProcessError as e:
print(f"执行命令时出错: {e.stderr}")
return set()
# 功能将软件包的nvr处理后返回name字符串
# 输入字符串name-version-release
# 返回值: 字符串name
def extract_name(nvr_string):
# 匹配形如 name-version-release 格式,只提取 name 部分
match = re.match(r"^(.*)-[^-]+-[^-]+$", nvr_string)
return match.group(1) if match else nvr_string
# 定义命令对列表,每个元素为 (组名, 命令1, 命令2)
command_pairs = [
(
"V10SP1.1 mips64el 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/mips64el/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/mips64el/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/mips64el/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/mips64el/ --check updates --all -n|grep \'package:\''
)
]
file_name = "package_comparison_mips64el.md"
with open(file_name, 'a') as f:
f.write(f"# 软件包依赖对比结果\n")
f.write(f"## 生成时间: {datetime.now()}\n\n")
for index, (group_name, command1, command2) in enumerate(command_pairs, 1):
package_list1 = get_package_list(command1)
package_list2 = get_package_list(command2)
more_packages = package_list2 - package_list1
less_packages = package_list1 - package_list2
names1 = [extract_name(nvr) for nvr in less_packages]
names2 = [extract_name(nvr) for nvr in more_packages]
more_names = set(names2) - set(names1)
with open(file_name, 'a') as f:
f.write(f"## {group_name} 对比结果\n\n")
f.write("### 已发布仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command1 + "\n")
f.write("```\n\n")
f.write("### 测试仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command2 + "\n")
f.write("```\n\n")
f.write("## 结果1已发布仓库报错列表比测试仓库列表多的包\n")
if less_packages:
for package in sorted(less_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果2测试仓库报错列表比已发布仓库列表多的包\n")
if more_packages:
for package in sorted(more_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果3结果2中列表比结果1中列表多的软件包的包名\n")
if more_names:
for name in sorted(more_names):
f.write(f"- {name}\n")
else:
f.write("\n")
f.write("\n---\n\n")
print(f"{group_name} 对比结果已输出到 {file_name} 文件中。")

View File

@@ -0,0 +1,132 @@
import subprocess
import re
from datetime import datetime
# 功能:检查某个产品的仓库中软件包的依赖报错
# 输入依赖检测的bash命令
# 返回值: 依赖报错软件包列表
def get_package_list(command):
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
output = result.stdout
pattern = r'package: (\S+)'
packages = re.findall(pattern, output)
return set(packages)
except subprocess.CalledProcessError as e:
print(f"执行命令时出错: {e.stderr}")
return set()
# 功能将软件包的nvr处理后返回name字符串
# 输入字符串name-version-release
# 返回值: 字符串name
def extract_name(nvr_string):
# 匹配形如 name-version-release 格式,只提取 name 部分
match = re.match(r"^(.*)-[^-]+-[^-]+$", nvr_string)
return match.group(1) if match else nvr_string
# 定义命令对列表,每个元素为 (组名, 命令1, 命令2)
command_pairs = [
(
"NS7.9 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/x86_64 --repofrompath updates,https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/x86_64 --repofrompath updates,https://update.cs2c.com.cn/private_test/repo/V7/V7Update9/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP1.1 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/x86_64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP2 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/x86_64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"V10SP3 2403 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/ --repofrompath updates,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath base,https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"HOSTOS x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"HOSTOS 2309 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"HOSTOS 2406 x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2406/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/HOST/2406/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/HOST/2406/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"V10 HPC x86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
),
(
"V10 86_64 对比",
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\'',
'yum clean all;dnf repoclosure --disablerepo="*" --repofrompath=base,https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/x86_64/ --repofrompath=updates,https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/x86_64/ --check updates --all -n|grep \'package:\''
)
]
file_name = "package_comparison_aarch64.md"
with open(file_name, 'a') as f:
f.write(f"# 软件包依赖对比结果\n")
f.write(f"## 生成时间: {datetime.now()}\n\n")
for index, (group_name, command1, command2) in enumerate(command_pairs, 1):
package_list1 = get_package_list(command1)
package_list2 = get_package_list(command2)
more_packages = package_list2 - package_list1
less_packages = package_list1 - package_list2
names1 = [extract_name(nvr) for nvr in less_packages]
names2 = [extract_name(nvr) for nvr in more_packages]
more_names = set(names2) - set(names1)
with open(file_name, 'a') as f:
f.write(f"## {group_name} 对比结果\n\n")
f.write("### 已发布仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command1 + "\n")
f.write("```\n\n")
f.write("### 测试仓库中执行的依赖检查命令\n")
f.write("```bash\n")
f.write(command2 + "\n")
f.write("```\n\n")
f.write("## 结果1已发布仓库中依赖报错软件包列表\n")
if less_packages:
for package in sorted(less_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果2测试仓库中依赖报错软件包列表\n")
if more_packages:
for package in sorted(more_packages):
f.write(f"- {package}\n")
else:
f.write("\n")
f.write("\n## 结果3结果2中列表比结果1中列表多的软件包的包名即测试仓库新增的报错包列表\n")
if more_names:
for name in sorted(more_names):
f.write(f"- {name}\n")
else:
f.write("\n")
f.write("\n---\n\n")
print(f"{group_name} 对比结果已输出到 {file_name} 文件中。")

View File

@@ -0,0 +1,22 @@
1.<2E><><EFBFBD><EFBFBD>˵<EFBFBD><CBB5>
<20>ֿ⼶<D6BF><E2BCB6><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>Լ<EFBFBD><D4BC><EFBFBD><E2A1A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
2.Ŀ¼<C4BF>
- compat
- compat.py
- repolist
- requirements
- README.md
compat.py <20><><EFBFBD><EFBFBD><EFBFBD>Խű<D4BD>
repolist <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʒ<EFBFBD>ֿ<EFBFBD><D6BF>б<EFBFBD>
3.<2E><>װ<EFBFBD><D7B0><EFBFBD><EFBFBD>
pip3 -r requirements
4.<2E><><EFBFBD>й<EFBFBD><D0B9><EFBFBD>
python3 compat.py
5.<2E><><EFBFBD><EFBFBD>˵<EFBFBD><CBB5>
<20><><EFBFBD><EFBFBD><EFBFBD>Ա<EFBFBD><D4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ¼<C4BF><C2BC><EFBFBD>£<EFBFBD>
/root/result/common/sp1/x86_64/repo-compare-report-20250425114902/repo.html
ʹ<><CAB9>parser_repo_json<6F><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
6.repolist<73>ṹ˵<E1B9B9><CBB5>
baseurl<72><6C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>base<73>ֿ<EFBFBD><D6BF><EFBFBD>ַ
updateurl<72><6C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>update<74>ֿ<EFBFBD><D6BF><EFBFBD>ַ
testurl<72><6C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֿ<EFBFBD><D6BF><EFBFBD>ַ

View File

@@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
"""compat.py"""
import concurrent.futures
import pandas as pd
import configparser
import subprocess
import threading
from datetime import datetime
from itertools import zip_longest
import time
import shutil
import json
import os
import re
config = configparser.ConfigParser()
config.read('repolist')
RESULT_DIR = "/root/result/common/"
RESULT_DIR1 = "/root/result/migrate/"
def prepare_dirs(dirs):
if os.path.exists(dirs):
shutil.rmtree(dirs)
os.makedirs(dirs)
def test_task(section, arch):
baseurl = config.get(section, 'baseurl')
updateurl = config.get(section, 'updateurl')
testurl = config.get(section, 'testurl')
origurl = ','.join([baseurl, updateurl])
work_dir = RESULT_DIR1 + section + '/' + arch
prepare_dirs(work_dir)
cmd = "/usr/bin/kylin_pkgchecker -output %s repo -l 2 -o %s -t %s -a %s" % (work_dir, updateurl, origurl, arch)
popen = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
text=True
)
stdout, stderr = popen.communicate()
sections = ['sp1', 'sp2', 'sp3', 'sp3-2403']
def test_repo_update(index):
start_index = index + 1
orig_arch = config.get(sections[index], 'arch')
orig_arch_list = re.split(',', orig_arch)
orig_baseurl = config.get(sections[index], 'baseurl')
orig_updateurl = config.get(sections[index], 'updateurl')
orig_testurl = ','.join([orig_baseurl, orig_updateurl])
for j in range(start_index,len(sections)):
target_baseurl = config.get(sections[j], 'baseurl')
target_updateurl = config.get(sections[j], 'updateurl')
target_arch = config.get(sections[j], 'arch')
target_testurl = ','.join([target_baseurl, target_updateurl])
target_arch_list = re.split(',',target_arch)
arch_list = list(set(orig_arch_list) & set(target_arch_list))
# print(arch_list)
for arch in arch_list:
work_dir = RESULT_DIR
# prepare_dirs(work_dir)
# print(sections[index],sections[j],arch)
cmd = "/usr/bin/kylin_pkgchecker -output %s repo -l 3 -o %s -t %s -a %s" % (work_dir, target_testurl, orig_testurl, arch)
popen = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
text=True
)
stdout, stderr = popen.communicate()
def test_repo_migrations():
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(test_repo_update, section) for section in range(len(sections))]
results = [future.result() for future in concurrent.futures.as_completed(futures)]
def parser_config(section):
arch = config.get(section,'arch')
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(test_task, section, a) for a in arch.split(',')]
results = [future.result() for future in concurrent.futures.as_completed(futures)]
def get_current_date():
timestamp = time.time()
localtime = time.localtime(timestamp)
now = time.strftime("%Y%m%d", localtime)
return now
def get_max_len():
pass
def write_excel(data):
col = ["产品", "架构", "依赖失败", "兼容失败", "源仓库", "目标仓库"]
whitedata = list(zip_longest(data[0], data[1], data[2] , list(set(data[3])), data[4] , data[5], fillvalue = None))
now = get_current_date()
excel_file = "仓库兼容性检测%s.xlsx" % now
sheet_name = "失败列表"
df = pd.DataFrame(whitedata, columns = col)
df.to_excel(
excel_file,
sheet_name = sheet_name,
index = False,
startrow = 0,
engine = "openpyxl"
)
def append_sheet_excel(data):
book = load_workbook()
sheet = book['失败列表']
last_row = sheet.max_row
append_df = pd.DataFrame(data)
def parser_repo_json():
"""
{"兼容":0,"NA":1,"不兼容":2,"待分析":3}
{"依赖完整":0,"依赖缺失":1,"NA":"NA"}
:return:
"""
#缺少依赖包列表
requirefaild = []
#兼容性失败包列表
compatfaild = []
data_list = []
# outfile = "/opt/repotest/repo.json"
filename = "repo.json"
for dirpath, dirname, files in os.walk("/var/www/html/output/20250425/result/"):
if filename in files:
outfile = os.path.join(dirpath, filename)
with open(outfile) as f:
data = json.load(f)
for k,v in data.items():
if k == 'repo_data':
for r in v:
if r["otherRequiresRet"] == 1:
requirefaild.append(r["name"])
if r["fileRet"] == 2 or r["cmdRet"] == 2 or r["serviceRet"] == 2 or r["configRet"] == 2 or r["abiRet"] == 2 or r["apiRet"] == 2:
compatfaild.append(r["name"])
if k == 'summary_data':
arch = v["sys_arch"]
origin_repo_url = v["origin_repo"]
target_repo_url = v["target_repo"]
maxlen = max(len(requirefaild), len(compatfaild))
archlist = [arch] * maxlen
origin_repo_url_list = [origin_repo_url] * maxlen
target_repo_url_list = [target_repo_url] * maxlen
product = ["Kylin-v7"] * maxlen
data_slist.append(product)
data_list.append(archlist)
data_list.append(requirefaild)
data_list.append(compatfaild)
data_list.append(origin_repo_url_list)
data_list.append(target_repo_url_list)
print(data_list)
write_excel(data_list)
def test_single_repo():
sections = config.sections()
prepare_dirs(RESULT_DIR)
for section in sections:
parser_config(section)
if __name__ == "__main__":
test_single_repo()

View File

@@ -0,0 +1,77 @@
[sp1]
baseurl=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/V10SP1.1/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/
arch=x86_64,aarch64,mips64el,loongarch64
[sp2]
baseurl=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/
arch=x86_64,aarch64
[sp3]
baseurl=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/
arch=x86_64,aarch64,loongarch64
[sp3-2403]
baseurl=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/
arch=x86_64,aarch64,loongarch64
[hpc]
baseurl=https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/HPC/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/
arch=x86_64,aarch64
[zj]
baseurl=https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/V10-ZJ/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/
arch=x86_64,aarch64
[2309A]
baseurl=https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/2309A/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/2309A/os/adv/lic/updates/
arch=aarch64
[2309B]
baseurl=https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/2309B/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/2309B/os/adv/lic/updates/
arch=aarch64
[HOST-2309]
baseurl=https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/HOST/2309/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/
arch=x86_64,aarch64
[HOST-2406]
baseurl=https://update.cs2c.com.cn/NS/HOST/2406/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/HOST/2406/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/HOST/2406/os/adv/lic/updates/
arch=x86_64,aarch64
[HOST]
baseurl=https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V10/HOST/SP3/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/
arch=x86_64,aarch64
[V7]
baseurl=https://update.cs2c.com.cn/NS/V17/V7Update9/os/adv/lic/base/
updateurl=https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/
testurl=https://update.cs2c.com.cn/private_test/repo/V7/V7Update9/os/adv/lic/updates/
arch=x86_64,aarch64
[8U8]
baseurl=https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/BaseOS/x86_64/os/,https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/AppStream/x86_64/os/
updateurl=https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/BaseOS-updates/x86_64/os/,https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/AppStream-updates/x86_64/os/
testurl=https://update.cs2c.com.cn/private_test/repo/V7/V7Update9/os/adv/lic/updates/
arch=x86_64

View File

@@ -0,0 +1,8 @@
pandas
configparser
subprocess
threading
itertools
shutil
time
json

View File

@@ -0,0 +1,22 @@
# 产品间升级测试
## 前置条件
- 确保测试机网络在重启后能自动连接
## 使用步骤
1. 将脚本传输到测试机
2. 执行升级工具:
```bash
python3 upgrade_tool_pri.py
```
3. 选择升级路径:
- 1) V10SP1.1 → V10SP2 → V10SP3 → V10SP3-2403
- 2) (V10SP1.1/V10SP2) → V10SP3 → V10SP3-2403
- 3) V10SP1.1直接升级V10SP2
- 4) (V10SP1.1/V10SP2)直接升级V10SP3
- 5) (V10SP1.1/V10SP2/V10SP3)直接升级V10SP3-2403
## 注意事项
- 脚本会生成守护进程`upgrade_tool.service`机器重启后仍会执行如果单步升级需要手动systemctl stop upgrade_tool.service
- 结果日志存放在`/var/log/`目录upgrade_tool_*.log

View File

@@ -0,0 +1,465 @@
import subprocess
import time
import sys
import shutil
import os
from datetime import datetime
# 全局系统架构变量
SYSTEM_ARCH = None
def get_log_file_path(arch):
"""获取当前升级路径对应的日志文件路径
Returns:
str: 日志文件路径
"""
choice, _, _ = load_state()
if choice and choice in UPGRADE_PATHS:
return f"/var/log/upgrade_tool_path{choice}_{arch}.log"
return "/var/log/upgrade_tool.log"
def log_message(message):
"""记录日志信息并打印到控制台
Args:
message (str): 要记录的日志消息
Returns:
None
"""
log_file = get_log_file_path(SYSTEM_ARCH)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
with open(log_file, "a") as f:
f.write(log_entry)
print(log_entry.strip()) # 输出带时间戳的日志格式
# 升级路径定义
UPGRADE_PATHS = {
"1": ["V10SP2", "V10SP3", "V10SP3-2403"],
"2": ["V10SP3", "V10SP3-2403"],
"3": ["V10SP2"],
"4": ["V10SP3"],
"5": ["V10SP3-2403"],
}
NKVERS_CHECK = {
"V10SP1.1": "SP1",
"V10SP2": "SP2",
"V10SP3": "SP3",
"V10SP3-2403": "Halberd",
}
# 每个版本对应的 repo 文件
REPO_CONTENTS = {
"V10SP2": """
[base]
name=Base Repo V10SP2
baseurl=https://10.44.16.185/NS/V10/V10SP2/os/adv/lic/base/x86_64/
enabled=1
gpgcheck=0
sslverify=False
[updates]
name=Updates Repo V10SP2
baseurl=https://10.44.16.185/private_test/repo/V10/V10SP2/os/adv/lic/updates/$basearch/
enabled=1
gpgcheck=0
sslverify=False
""",
"V10SP3": """
[base]
name=Base Repo V10SP3
baseurl=https://10.44.16.185/NS/V10/V10SP3/os/adv/lic/base/x86_64/
enabled=1
gpgcheck=0
sslverify=False
[updates]
name=Updates Repo V10SP3
baseurl=https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/$basearch/
enabled=1
gpgcheck=0
sslverify=False
""",
"V10SP3-2403": """
[base]
name=Base Repo V10SP3-2403
baseurl=https://10.44.16.185/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/
enabled=1
gpgcheck=0
sslverify=False
[updates]
name=Updates Repo V10SP3-2403
baseurl=https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/$basearch/
enabled=1
gpgcheck=0
sslverify=False
"""
}
YUM_REPO_DIR = "/etc/yum.repos.d"
def run_command(cmd, check=True):
"""执行系统命令并记录输出
Args:
cmd (str): 要执行的命令
check (bool): 如果为True命令失败会退出程序
Returns:
str: 命令的完整输出
"""
log_message(f"\n>>> 执行命令:{cmd}")
# 使用Popen实时输出
process = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1) # 行缓冲
output_lines = []
while True:
# 实时读取输出
line = process.stdout.readline()
if not line and process.poll() is not None:
break
if line:
line = line.strip()
print(line) # 实时打印
output_lines.append(line)
# 检查返回码
returncode = process.wait()
if returncode != 0 and check:
error_msg = f"❌ 命令执行失败:{process.stderr.read().strip()}"
log_message(error_msg)
sys.exit(1)
full_output = "\n".join(output_lines)
log_message(f"命令完整输出:\n{full_output}")
return full_output
def get_system_arch():
"""获取系统架构并返回对应的repo架构字符串
Returns:
str: 系统架构(x86_64/aarch64/loongarch64)
"""
arch = run_command("uname -m").strip()
# 架构映射
arch_map = {
"x86_64": "x86_64",
"aarch64": "aarch64",
"loongarch64": "loongarch64",
}
# 查找匹配的架构
for key, value in arch_map.items():
if key in arch:
return value
log_message(f"⚠️ 未知架构: {arch}, 默认使用x86_64")
return "x86_64" # 默认回退
def write_repo_file(version):
"""写入指定版本的YUM源配置文件
Args:
version (str): 目标版本(V10SP2/V10SP3/V10SP3-2403)
Returns:
None
"""
log_message(f"📦 配置 {version} 的 YUM 源")
# 获取系统架构并替换URL
arch = get_system_arch()
repo_content = REPO_CONTENTS[version].replace("x86_64", arch)
# 备份现有repo文件
for f in os.listdir(YUM_REPO_DIR):
if f.endswith(".repo") and not f.endswith(".bak"):
src = os.path.join(YUM_REPO_DIR, f)
dst = os.path.join(YUM_REPO_DIR, f"{f}.bak")
if os.path.exists(dst):
os.remove(dst)
os.rename(src, dst)
log_message(f"🔁 已备份: {f} -> {f}.bak")
# 写入新repo文件
new_repo_path = os.path.join(YUM_REPO_DIR, f"{version}.repo")
with open(new_repo_path, "w") as f:
f.write(repo_content)
log_message(f"✅ 已写入新 repo 文件:{new_repo_path}")
def print_progress(current, total):
progress = int((current / total) * 50)
print(f"\n[{'=' * progress}{' ' * (50 - progress)}] {current}/{total}")
def upgrade_to_version(version, current_idx=None, total_steps=None):
"""执行升级到指定版本的操作
Args:
version (str): 目标版本
current_idx (int, optional): 当前步骤索引
total_steps (int, optional): 总步骤数
Returns:
None
"""
if current_idx is not None and total_steps is not None:
print_progress(current_idx, total_steps)
log_message(f"\n🔧 开始升级到版本 {version} ...")
# 在备份yum源前先执行系统更新
log_message("🔄 正在执行系统更新(dnf -y update)...")
run_command("dnf -y update")
log_message("✅ 系统更新完成")
write_repo_file(version)
run_command("dnf clean all")
run_command("dnf makecache")
run_command("dnf -y update")
# 在重启前验证版本升级是否成功
log_message("🔍 验证升级结果...")
check_version(version)
# 在dnf update成功后更新状态(仅多步升级)
if current_idx is not None and total_steps > 1:
choice, versions, _ = load_state()
save_state(choice, versions, current_idx + 1)
log_message("🔁 系统即将重启...")
run_command("reboot", check=False)
def wait_for_reboot(timeout=300):
"""等待系统重启并恢复上线
Args:
timeout (int): 超时时间(秒)默认300秒
Returns:
None
"""
log_message("⏳ 等待系统重启并恢复上线...")
time.sleep(10)
elapsed = 0
while elapsed < timeout:
try:
run_command("ping -c 1 127.0.0.1", check=False)
# 系统上线后重启网络服务
try:
run_command("ifdown ens192 && ifup ens192", check=False)
log_message("🔄 网络服务已重启")
except:
log_message("⚠️ 网络服务重启失败,继续执行")
log_message("✅ 系统已上线")
time.sleep(10)
return
except:
time.sleep(5)
elapsed += 5
log_message("❌ 系统未能在预期时间内恢复,请检查虚拟机状态")
sys.exit(1)
def check_version(expected):
"""验证当前系统版本是否符合预期
Args:
expected (str): 预期的版本号
Returns:
None
"""
log_message("🔍 正在验证版本...")
keyword = NKVERS_CHECK.get(expected, "")
if not keyword:
print(f"❌ 未知版本: {expected}")
sys.exit(1)
# 使用grep直接检查关键字
try:
run_command(f"nkvers | grep -q '{keyword}'")
print(f"✅ 升级成功,版本特征 '{keyword}' 验证通过")
# 仅在最后一步升级完成后清除状态
if os.path.exists(STATE_FILE):
with open(STATE_FILE, "r") as f:
lines = f.read().splitlines()
if len(lines) >= 2 and int(lines[1]) == len(lines[2:]):
clear_state()
except:
print(f"❌ 升级失败,缺少版本特征关键字: '{keyword}'")
sys.exit(1)
def print_menu():
"""打印升级路径选择菜单
Returns:
None
"""
log_message("\n请选择升级路径:")
print("1) V10SP1.1 -> V10SP2 -> V10SP3 -> V10SP3-2403")
print("2) (V10SP1.1/V10SP2)--> V10SP3 --> V10SP3-2403")
print("3) V10SP1.1直接升级 V10SP2")
print("4) (V10SP1.1/V10SP2)直接升级 V10SP3")
print("5) (V10SP1.1/V10SP2/V10SP3)直接升级 V10SP3-2403")
print("q) 退出")
STATE_FILE = "/var/lib/upgrade_tool.state"
def save_state(choice, versions, current_idx):
"""保存升级状态到文件
Args:
choice (str): 用户选择的升级路径
versions (list): 升级路径版本列表
current_idx (int): 当前步骤索引
Returns:
None
"""
with open(STATE_FILE, "w") as f:
f.write(f"# 用户选择的升级路径\n{choice}\n")
f.write(f"# 当前执行的步骤索引(从0开始)\n{current_idx}\n")
f.write("# 升级路径版本列表\n")
f.write("\n".join(versions) + "\n")
def load_state():
"""从文件加载升级状态
Returns:
tuple: (choice, versions, current_idx) 或 (None, None, None)
"""
if not os.path.exists(STATE_FILE):
return None, None, None
with open(STATE_FILE, "r") as f:
# 跳过注释行,只读取实际数据行
lines = [line for line in f.read().splitlines()
if not line.startswith("#")]
if len(lines) < 2:
return None, None, None
choice = lines[0]
current_idx = int(lines[1])
versions = lines[2:]
return choice, versions, current_idx
def clear_state():
"""清除升级状态文件
Returns:
None
"""
if os.path.exists(STATE_FILE):
os.remove(STATE_FILE)
def install_as_service():
"""将脚本安装为系统服务
Returns:
None
"""
service_content = f"""[Unit]
Description=System Upgrade Tool
After=network.target
Wants=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 {os.path.abspath(__file__)}
# 仅在非正常退出时重启(不包含正常退出码0)
Restart=on-failure
RestartSec=10s
User=root
[Install]
WantedBy=multi-user.target
"""
service_path = "/etc/systemd/system/upgrade_tool.service"
with open(service_path, "w") as f:
f.write(service_content)
run_command("systemctl daemon-reload")
run_command("systemctl enable upgrade_tool.service")
log_message("✅ 已安装为系统服务")
def main():
"""主函数,处理升级流程
Returns:
None
"""
global SYSTEM_ARCH
SYSTEM_ARCH = get_system_arch()
# 确保日志目录存在
os.makedirs(os.path.dirname(get_log_file_path(SYSTEM_ARCH)), exist_ok=True)
log_message("=== 升级工具启动 ===")
if not shutil.which("nkvers"):
log_message("⚠️ 未找到 nkvers 命令,请确保它在 PATH 中")
sys.exit(1)
# 如果是首次运行,安装为服务
if not os.path.exists("/etc/systemd/system/upgrade_tool.service"):
install_as_service()
# 检查是否有未完成的升级任务
choice, versions, current_idx = load_state()
if choice and versions and current_idx < len(versions):
log_message(f"🔍 检测到未完成的升级任务,正在恢复...")
# 先验证上一步的升级结果(如果有)
if current_idx > 0:
prev_version = versions[current_idx - 1]
log_message(f"⏳ 验证前一步升级结果: {prev_version}")
check_version(prev_version)
# 立即保存状态,防止重启后丢失进度
save_state(choice, versions, current_idx)
# 继续后续升级步骤
for version in versions[current_idx:]:
upgrade_to_version(version, current_idx, len(versions))
wait_for_reboot()
return
if choice and versions and current_idx == len(versions):
clear_state()
log_message("\n🎉 全部升级步骤完成!\n")
sys.exit(0) # 明确退出防止服务重启
while True:
print_menu()
choice = input("请输入选项编号: ").strip()
if choice.lower() == "q":
print("👋 已退出。")
sys.exit(0) # 明确退出防止服务重启
elif choice in UPGRADE_PATHS:
versions = UPGRADE_PATHS[choice]
# 仅多步升级创建状态文件
if len(versions) > 1:
save_state(choice, versions, 0)
for idx, version in enumerate(versions):
upgrade_to_version(version, idx, len(versions))
wait_for_reboot()
if len(versions) > 1:
clear_state()
print("\n🎉 全部升级步骤完成!\n")
else:
print("⚠️ 无效输入,请重新选择。")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,21 @@
脚本简介
本脚本用于测试增量或者全量V10SPx 系列产品仓库中软件包版本对比。
读取 “每周仓库更新内测清单-20250513-1.xlsx”里的bug以及cve sheet里的 中的源码包全名、关联产品、涉及架构三列。
使用方法
python3 check_update_prod_final.py
输出文件
output-0513-x86.csv output-0513-arm.csv output-0513-loong.csv
csv里面内容有包名 版本信息 关联产品 涉及架构 对比内容 对比结果 测试用例版本 _调试信息
筛选出对比结果为error,upgrade的内容提交到每周仓库更新内测清单的测试报错汇总表格中
注意事项
1.check_update_prod_final.py中引入的repo_cmp_20250103.py和repodata.py按照实际路径写入
2.check_update_prod_final.py中file_path和sheet_name读取的文件按照实际路径填写

View File

@@ -0,0 +1,350 @@
# -*- coding:utf-8 -*-
# Time:2025/1/3 14:13
# Author:GuoChao
# FileName:check_update.py
import openpyxl
import rpm_vercmp
from repo_cmp_20250103 import plus,bubbleSort
import csv
from collections import defaultdict
from repodata import write_to_csv
def update_test_results(file_path, sheet_name):
"""
读取Excel文件中指定工作表的数据并更新测试结果。
现在返回的是列表嵌套字典,字典的键是列标题,值是具体数据。
:param file_path: Excel文件路径
:param sheet_name: 工作表名称
"""
# 打开Excel文件
wb = openpyxl.load_workbook(file_path, data_only=True, read_only=True)
# 选择指定工作表
if sheet_name not in wb.sheetnames:
raise ValueError(f"工作表 '{sheet_name}' 不存在。")
sheet = wb[sheet_name] # 使用工作表名称选择
# 假设第一行是标题(列名)
headers = [sheet.cell(row=1, column=col).value for col in range(1, sheet.max_column + 1)]
# 存储每行数据的字典列表
test_case_list = []
# 假设读取B, C, O列
#columns_to_read = [2, 3, 13] # B列、C列、O列
columns_to_read = [1, 2, 3]
for row in range(2, sheet.max_row + 1): # 从第二行开始读取(跳过标题行)
test_case_dict = {headers[col-1]: sheet.cell(row=row, column=col).value for col in columns_to_read}
test_case_list.append(test_case_dict)
return test_case_list
def process_dicts_in_list(dict_list):
"""
遍历列表中的字典,并对每个字典的元素进行处理。
:param dict_list: 一个包含字典的列表
:return: 处理后的字典列表
"""
processed_list = []
# 遍历字典列表
for dictionary in dict_list:
processed_dict = {}
try:
# 提取源码包的版本信息
#package_name = dictionary['源码包全名'].rsplit('-', 2)[0] # 提取包名
package_name = dictionary['源码包全名'] and dictionary['源码包全名'].rsplit('-', 2)[0] or '未知包名'
#package_version = dictionary['源码包全名'].rsplit('-', 2)[1] + "-" + dictionary['源码包全名'].rsplit('-', 2)[2].rsplit('.', 3)[0] # 提取版本
package_version = (dictionary['源码包全名'] and dictionary['源码包全名'].rsplit('-', 2)[1] + "-" +
dictionary['源码包全名'].rsplit('-', 2)[2].rsplit('.', 3)[0]) or '未知版本'
except (IndexError, KeyError) as e:
# 错误处理:如果数据格式不正确或缺少关键字段,跳过该条记录
print(f"错误处理字典 {dictionary}: {e}")
continue
# 获取关联产品和涉及架构
package_product = dictionary.get('关联产品', 'N/A') # 使用 get 防止 KeyError
package_arch = dictionary.get('涉及架构', 'N/A')
# 将处理后的信息存储到字典中
processed_dict['包名'] = package_name
processed_dict['版本信息'] = package_version
processed_dict['关联产品'] = package_product
processed_dict['涉及架构'] = package_arch
# 将处理后的字典添加到列表中
processed_list.append(processed_dict)
return processed_list
def extract_full_version(srpm_name):
"""从SRPM文件名中提取完整版本信息包含所有必要部分"""
if not srpm_name:
return None
try:
# 移除.src.rpm后缀
base = srpm_name.replace('.src.rpm', '')
# 分割架构部分
if '.' in base:
main_part, _ = base.rsplit('.', 1) # 丢弃架构信息
else:
main_part = base
# 分割版本和发行号
if '-' in main_part:
# 从右边找到第一个连字符分割
parts = main_part.rsplit('-', 1)
version_part = parts[0].split('-')[-1] # 获取真正的版本部分
release_part = parts[1]
return f"{version_part}-{release_part}"
return None
except Exception as e:
print(f"版本解析错误 [{srpm_name}]: {str(e)}")
return None
def process_dicts_in_list(dict_list):
"""处理原始数据,使用新的版本提取方法"""
processed_list = []
for dictionary in dict_list:
processed_dict = {}
try:
full_name = dictionary.get('源码包全名', '')
# 提取包名(保留原始逻辑)
package_name = full_name.rsplit('-', 2)[0] if full_name else '未知包名'
# 使用新方法提取版本
package_version = extract_full_version(full_name) or '未知版本'
processed_dict.update({
'包名': package_name,
'版本信息': package_version,
'关联产品': dictionary.get('关联产品', 'N/A'),
'涉及架构': dictionary.get('涉及架构', 'N/A')
})
processed_list.append(processed_dict)
except Exception as e:
print(f"处理记录出错: {str(e)}")
continue
return processed_list
def check_version(processed_list, productinfo):
"""版本对比逻辑:仅对比测试用例关联产品对应的仓库"""
for processed_dict in processed_list:
# 获取当前测试用例的关联产品名称
current_product = processed_dict.get('关联产品', '未知产品')
# 如果产品不存在或未配置仓库,跳过
if current_product not in productinfo:
processed_dict.update({
'对比内容': "无关联产品信息",
'对比结果': "N/A"
})
continue
# 仅获取当前关联产品对应的仓库数据
pkglist, filename = productinfo[current_product]['repolist']
# 收集所有版本及所属产品(用于展示)
all_versions = {}
# 按产品线分组的版本(用于比较)
product_line_versions = defaultdict(list)
# 仅遍历当前产品的仓库
for pkg in filename:
if pkg['srcname'] == processed_dict['包名']:
ver = extract_full_version(pkg['sourcerpm'])
if ver:
# 获取该包所属的产品线如V10SP3-2403
product_line = pkg.get('product', 'unknown')
# 收集展示数据
if ver not in all_versions:
all_versions[ver] = set()
all_versions[ver].add(product_line)
# 收集比较数据
product_line_versions[product_line].append(ver)
# ... 其余逻辑保持不变 ...
# 计算各产品线的最高版本
max_versions = {}
for product_line, versions in product_line_versions.items():
sorted_versions = bubbleSort(versions)
if sorted_versions:
max_versions[product_line] = sorted_versions[-1]
# 执行版本比较
test_case_ver = processed_dict['版本信息']
result = "无匹配版本"
error_flag = False
all_higher = True # 假设所有产品线版本都比测试用例高
if max_versions:
for product_line, max_ver in max_versions.items():
try:
cmp = rpm_vercmp.vercmp(test_case_ver, max_ver)
if cmp > 0:
error_flag = True # 发现任一产品线版本低于测试用例
break
if cmp >= 0:
all_higher = False # 存在不高于的情况
except Exception as e:
print(f"版本比较错误: {str(e)}")
if error_flag:
result = "error,upgrade"
elif all_higher:
result = "downgrade"
else:
result = "equal"
# 生成对比内容(保持原始顺序)
sorted_versions = bubbleSort(list(all_versions.keys()))
compared_content = []
for ver in sorted_versions:
products = '|'.join(all_versions[ver])
compared_content.append(f"{ver} ({products})")
# 更新结果
processed_dict.update({
'对比内容': '\n'.join(compared_content) or "无匹配版本",
'对比结果': result,
'测试用例版本': test_case_ver,
'_调试信息': ' | '.join([f"{k}:{v}" for k, v in max_versions.items()]) # 可选调试字段
})
return processed_list
def parse_product_from_url(url):
"""
从仓库URL中解析产品名称例如V10SP1
示例URLhttps://update.cs2c.com.cn/NS/V10/V10SP1/os/adv/lic/base/x86_64/
"""
parts = url.strip('/').split('/')
try:
v10_index = parts.index('V10')
if v10_index + 1 < len(parts):
return parts[v10_index + 1]
except ValueError:
pass
return '未知产品'
# 示例调用
#file_path = r'C:/Users/lenovo.DESKTOP-JAA702O/Desktop/update内网源集成测试单-20241231.xlsx'
#file_path = r'C:/Users/Lenovo/Desktop/update内网源集成测试单-20250318.xlsx'
file_path = r'/root/mxh/每周仓库更新内测清单-20250513-1.xlsx'
#file_path = r'D:\Desktop\sp1.1loon.xlsx'
#file_path = r'D:\Desktop\0422.xlsx'
#sheet_name = 'bug【20250415功能测试】' # 工作表名称
sheet_name = 'x86'
#sheet_name = 'cve【20250408】'
#sheet_name = 'bug【20250325测试结果统计】' # 工作表名称
# 定义产品名称与对应 URL 列表的映射
product_urls = {
"银河麒麟高级服务器操作系统 V10 SP3": [
"https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/",
"https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/"
# "https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/aarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/aarch64/"
# "https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/loongarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/loongarch64"
],
"银河麒麟高级服务器操作系统 V10 SP2": [
"https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/",
"https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/",
"https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/",
"https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/x86_64/"
# "https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/aarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/aarch64/",
# "https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/aarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/aarch64/"
# "https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/loongarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/",
# "https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/loongarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/loongarch64/"
],
"银河麒麟高级服务器操作系统 V10 SP1": [
"https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/x86_64/",
"https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/",
"https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/",
"https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/x86_64/",
"https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/x86_64/",
"https://10.44.16.185/private_test/repo/V10/V10SP2/os/adv/lic/updates/x86_64/"
# "https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/aarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/aarch64/",
# "https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/aarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/aarch64/",
# "https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/aarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP2/os/adv/lic/updates/aarch64/",
# "https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/base/loongarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/",
# "https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/loongarch64/",
# "https://10.44.16.185/private_test/repo/V10/V10SP3/os/adv/lic/updates/loongarch64/"
]
}
# 动态生成 products_info关键修改部分
products_info = {}
for product_name, urls in product_urls.items():
pkglist_total = []
filename_total = []
for url in urls:
# 调用 plus 处理单个 URL返回 (pkglist_part, filename_part)
pkglist_part, filename_part = plus(url)
# 解析当前 URL 的产品名称(例如 V10SP1
product = parse_product_from_url(url)
# 为每个软件包字典添加 product 字段
for pkg in filename_part:
pkg['product'] = product # 添加关键字段
# 合并数据
filename_total.extend(filename_part)
pkglist_total.extend(pkglist_part)
# 更新 products_info
products_info[product_name] = {
'repolist': (pkglist_total, filename_total)
}
result = update_test_results(file_path, sheet_name)
processed_list=process_dicts_in_list(result)
processed_list=check_version(processed_list,products_info)
def write_to_csv(processed_list, output_filename):
"""
将处理后的数据写入 CSV 文件
"""
# 动态收集所有字段名(保持顺序)
fieldnames = []
seen = set()
for item in processed_list:
for key in item.keys():
if key not in seen:
seen.add(key)
fieldnames.append(key)
# 写入 CSV 文件
with open(output_filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for row in processed_list:
# 填充缺失字段为空值
full_row = {key: row.get(key, "") for key in fieldnames}
writer.writerow(full_row)
print(f"数据已保存至: {output_filename}")
write_to_csv(processed_list,'output-0515-x86.csv')

View File

@@ -0,0 +1,759 @@
# -*- coding:utf-8 -*-
# Time:2025/1/3 9:16
# Author:GuoChao
# FileName:repo_cmp_20250103.py
import requests
from lxml import etree
import rpm_vercmp
import xlsxwriter
import datetime
import re
import xlwt
import os
import subprocess
import logging
logging.captureWarnings(True)
from repodata import plus,getsrc
#####获取软件包列表#####
def openfile(*files): # 方法1用于从文件中获取软件包列表
pkglist = []
for file in files: # 对多个文件进行处理
with open(file, "r", encoding='utf-8') as output: # 打开文件
for line in output.readlines(): # 依次读取每行
if ".rpm" in line: # 去除非rpm包的影响 也可以修改为判断其他关键字
pkglist.append(line.strip()) # 去除每行头尾的空白
print('\n' + '###### 00 通过 Openfile 获取软件包列表完成,软件包数量:', len(pkglist), '#####')
return pkglist
def gethtml(*baseurls): #方法2从html文件解析软件包列表
head = {
"User-Agent": "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122 Safari / 537.36"}
pkglist = []
for baseurl in baseurls: # 对多个url进行处理
response = requests.get(baseurl, headers=head,verify=False)
# 解决 requests.exceptions.SSLError 请求异常SSL错误证书认证失败问题进行解决
html = response.text
html = etree.HTML(html)
a_links = html.xpath("//a") # 查找所有 a 标签 ,//a 代表 html 下所有 a,html/body/a 同理
for item in a_links:
item1 = item.get('href') # 获取a标签某一属性
if ".rpm" in item1: # 去除非rpm包的影响 也可以修改为判断其他关键字,备选.el8
item1 = item1.replace("%2B", "+") # 对url不能识别”+“进行修正
pkglist.append(item1)
print('\n' + '##### 00 通过 Gethtml 获取软件包列表完成,软件包数量:', len(pkglist), '#####')
return pkglist
#####对仓库软件包进行分析#####
def rjbfx(lab,listA): #对软件包构成进行分析
# 首先对总的列表进行分析
# 区分i686和非i686
i686l=[x for x in listA if ".i686" in x]
fi686l=[x for x in listA if ".i686" not in x]
# 区分mbs和非mbs软件包
mbs1 = [x for x in listA if ".module" in x]
fmbs1 = [x for x in listA if ".module" not in x]
print('\n' + '##### 01 通过 Rjbgfh 分析 ' + lab + ' 软件包完成,涉及软件包总数量:', len(listA), '#####')
print("\n" + "其中涉及i686相关包数量:", len(i686l), "\n" + "其中涉及非i686相关软件包数量:", len(fi686l))
print("其中涉及模块包数量:", len(mbs1), "\n" + "其中涉及非模块包数量:", len(fmbs1))
# 其次对其中i686软件包进行处理
mbs2 = [x for x in i686l if ".module" in x]
fmbs2 = [x for x in i686l if ".module" not in x]
print('\n' + '##### 01 通过 Rjbgfh 分析 '+lab+' i686 软件包完成,涉及软件包数量:', len(i686l), '#####')
print("其中涉及模块包数量:", len(mbs2),"\n" + "其中涉及非模块包数量:", len(fmbs2))
# 最后对其中非i686软件包进行处理
mbs3 = [x for x in fi686l if ".module" in x]
fmbs3 = [x for x in fi686l if ".module" not in x]
print('\n' + '##### 01 通过 Rjbgfh 分析 '+lab+' 非i686 软件包完成,涉及软件包数量:', len(fi686l), '#####')
print("其中涉及模块包数量:", len(mbs3),"\n" + "其中涉及非模块包数量:", len(fmbs3))
return listA,mbs1,fmbs1,i686l,mbs2,fmbs2,fi686l,mbs3,fmbs3
#####严格模式比较#####
def ygjgfl(pkglist): #对严格模式处理下的结果进行分类
name = []
types = []
arch = []
for pkg in pkglist:
name.append(pkg)
if ".module" in pkg and ".ks8" in pkg: # 对软件包类型、以及是否产品化进行判断
types.append("模块包(产品化)")
elif ".module" in pkg:
types.append("模块包")
elif ".ks8" in pkg:
types.append("非模块包(产品化)")
else:
types.append("非模块包")
# 对软件包类型进行判断
if ".i686" in pkg:
arch.append("i686")
elif ".x86_64" in pkg:
arch.append("x86_64")
elif ".noarch" in pkg:
arch.append("noarch")
elif ".aarch64" in pkg:
arch.append("aarch64")
elif ".src" in pkg:
arch.append("src")
else:
arch.append("N/A")
mks=types.count("模块包(产品化)")+types.count("模块包")
fmks=types.count("非模块包")+types.count("非模块包(产品化)")
return name,types,arch,mks,fmks
def comp1(labA,labB,listA1,listB1): #严格模式下比较
listA=[]
for pkg in listA1:
# if ".oe" in pkg:
# pkg1 = pkg.replace(".oe1|oe2003sp4", "")
# elif ".ky10" in pkg:
# pkg1 = pkg.replace(".ky10|.p\d{2}.ky10", "")
# else:
# pkg1=pkg
pkg1=pkg.rsplit("-",1)[0]+"-"+pkg.rsplit("-",1)[-1].split(".",1)[0]
listA.append(pkg1)
listB = []
for pkg in listB1:
pkg1 = pkg.rsplit("-", 1)[0] + "-" + pkg.rsplit("-", 1)[-1].split(".", 1)[0]
# if ".oe" in pkg:
# pkg1 = pkg.replace(".oe1|oe2003sp4", "")
# elif ".ky10" in pkg:
# pkg1 = pkg.replace(".ky10|.p\d{2}.ky10", "")
# else:
# pkg1 = pkg
listB.append(pkg1)
listA = list(set(listA))
print("去重后A",len(listA))
listB= list(set(listB))
print("去重后B" ,len(listB))
common_pkg = [x for x in listA if x in listB] # 整体ab都有
incommon_pkg1 = [x for x in listA if x not in listB] # 整体a有b没有
incommon_pkg2 = [x for x in listB if x not in listA] # 整体b有a没有
ABtong,leixing1,arch1,mk1,fmk1=ygjgfl(common_pkg) # 对相同包进行分类
Ashao,leixing2,arch2,mk2,fmk2=ygjgfl(incommon_pkg2) # 对A的少包进行分类 B独有
Aduo,leixing3,arch3,mk3,fmk3=ygjgfl(incommon_pkg1) # 对A多的包进行分类 A独有
print('\n'+"##### 02 严格模式 软件包对比开始 #####")
print(labA+""+labB+"相同的软件包数量:",len(ABtong),"\n"+'模块包:',leixing1.count("模块包(产品化)")+leixing1.count("模块包"),"\n"+'其中非模块包:',leixing1.count("非模块包")+leixing1.count("非模块包(产品化)"))
print(labA+""+labB+"少的软件包数量:",len(Ashao),"\n"+'模块包:',leixing2.count("模块包(产品化)")+leixing2.count("模块包"),"\n"+'其中非模块包:',leixing2.count("非模块包")+leixing2.count("非模块包(产品化)"))
print(labA+""+labB+"多的软件包数量:",len(Aduo),"\n"+'模块包:',leixing3.count("模块包(产品化)")+leixing3.count("模块包"),"\n"+'其中非模块包:',leixing3.count("非模块包")+leixing3.count("非模块包(产品化)"))
print("##### 02 严格模式 软件包对比完成 #####"+'\n')
return Aduo,leixing3,Ashao,leixing2,ABtong,leixing1,arch1,arch2,arch3,mk1,fmk1,mk2,fmk2,mk3,fmk3
#####宽松模式比较#####
def bubbleSort(arr): # 对同一仓库的多版本进行冒泡排序#
n = len(arr)
for i in range(n): # 遍历所有数组元素
for j in range(0, n - i - 1): # Last i elements are already in place
if rpm_vercmp.vercmp(arr[j], arr[j + 1]) == 1:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
def getnvra(data): # 获取软件包列表nvra
filename=[]
for i in data:
(n, v, r, a) = (i.rsplit('-', 2)[0], i.rsplit('-', 2)[1], i.rsplit('-', 2)[2].rsplit('.', 2)[0], i.rsplit('.', 2)[1]) # 切片方式获取软件包nvra
filename1 = {'name': n, 'version': v, 'release': r, 'arch': a}
# (n, v, r) = (i.rsplit('-', 2)[0], i.rsplit('-', 2)[1], i.rsplit('-', 2)[2].rsplit('.', 2)[0]) # 切片方式获取软件包nvr
# filename1 = {'name': n, 'version': v, 'release': r}
filename.append(filename1)
print('\n'+'##### 02 通过 getnvra 获取软件包nvr完成软件包个数:',len(filename),'#####')
return filename
def relgfh(j): #对release进行统一规范化修改
if ".module" in j: # 对模块包进行统一修正
j = j.replace(".module+", ".module_") # 对红帽模块、龙蜥包命名进行修改
#j = j.rsplit('+', 2)[0] # 仅去除模块包release中 +10155+7f691889 字段
j = j.split('_', 1)[0] # 去除模块包relases中 +/_el8.2.0+10155+7f691889 字段,去除模块el8.x平台影响
result = re.search(".ks8.\d{2}", j) # 去除 ks/ns 产品化影响###
if result:
result = result.group()
j = j.replace(str(result), "")
elif ".oe" in j: # 对oe进行统一修正
#j = j.replace(".module+", ".module_")
#j = j.rsplit('+', 2)[0] # 仅去除模块包release中 +10155+7f691889 字段
j = j.split('_', 1)[0] # 去除模块包relases中 +/_el8.2.0+10155+7f691889 字段,去除模块el8.x平台影响
# result = re.search(".oe\d{8}", j) # 去除 ks/ns 产品化影响###
result = re.search(".oe1", j) # 去除 ks/ns 产品化影响###
if result:
result = result.group()
j = j.replace(str(result), "")
else: # # 对非模块包进行统一修正
j = j.replace("ky10", "")
result = re.search(".ks8.\d{2}|.ns7.\d{2}|.p\d{2}", j) # 去除 ks/ns/p 产品化影响###
if result:
result = result.group()
j = j.replace(str(result), "")
return j
def ksjgfl(namelist,data):
pkglist = []
types=[]
arch=[]
for i in namelist:
rpmname = []
for j in data:
if j["name"] == i:
rpmname.append(j["name"] + "-" + j["version"] + "-" + j["release"] + "." + j["arch"]+ "." + "rpm")
#rpmname.append(j["name"] + "-" + j["version"] + "-" + j["release"])
rpmname = bubbleSort(rpmname)
#print(rpmname)
if ".module" in rpmname[-1] and ".ks8" in rpmname[-1]:
types.append("模块包(产品化)")
elif ".module" in rpmname[-1]:
types.append("模块包")
elif ".ks8" in rpmname[-1]:
types.append("非模块包(产品化)")
else:
types.append("非模块包")
if ".i686" in rpmname[-1]:
arch.append("i686")
elif ".x86_64" in rpmname[-1]:
arch.append("x86_64")
elif ".noarch" in rpmname[-1]:
arch.append("noarch")
elif ".aarch64" in rpmname[-1]:
arch.append("aarch64")
elif ".src" in rpmname[-1]:
arch.append("src")
else:
arch.append("N/A")
pkglist.append("\n".join(rpmname))
return namelist,pkglist,types,arch
def comp2(labA,labB,listA,listB):
data1 = getnvra(listA)
data2 = getnvra(listB)
listA_keys = []
listB_keys = []
for i in data1:
key = i["name"]
listA_keys.append(key)
for i in data2:
key = i["name"]
listB_keys.append(key)
common_keys = [x for x in listA_keys if x in listB_keys]
icommon_keys1 = [x for x in listA_keys if x not in common_keys]
icommon_keys2 = [x for x in listB_keys if x not in common_keys]
common_keys = list(set(common_keys))
icommon_keys1 = list(set(icommon_keys1))
icommon_keys2 = list(set(icommon_keys2))
tongming,pkglist1,leixing4,arch6 = ksjgfl(common_keys, data1)
Aqueshi1,Aqueshi2,leixing5,arch4 = ksjgfl(icommon_keys2,data2)
Bqueshi1,Bqueshi2,leixing6,arch5 = ksjgfl(icommon_keys1, data1)
shengjiang = []
pkglist2 = []
# 对AB同名软件包进行处理
for i in common_keys:
ccc = []
for dict1 in data1:
if dict1["name"] == i:
ccc.append(dict1["name"] + "-" + dict1["version"] + "-" + dict1["release"]+ "." + dict1["arch"] + "." + "rpm")
ccc = bubbleSort(ccc)
list1_release = ccc[-1].rsplit('-', 2)[2].rsplit('.', 2)[0]
release1 = relgfh(list1_release)
#print(release1)
list1_version = ccc[-1].rsplit('-', 2)[1] + '-' + release1
list1_arch = ccc[-1].rsplit('.', 2)[1]
ddd = []
for dict2 in data2:
if dict2["name"] == i:
ddd.append(dict2["name"] + "-" + dict2["version"] + "-" + dict2["release"] + "." + dict2["arch"] + "." + "rpm")
ddd = bubbleSort(ddd)
list2_version = ddd[-1].rsplit('-', 2)[2].rsplit('.', 2)[0]
release2 = relgfh(list2_version)
#print(release2)
list2_version = ddd[-1].rsplit('-', 2)[1] + '-' + release2
list2_arch = ddd[-1].rsplit('.', 2)[1]
pkglist2.append("\n".join(ddd))
if rpm_vercmp.vercmp(list1_version, list2_version) == 1 and list1_arch == list2_arch:
shengjiang.append("upgrade")
elif rpm_vercmp.vercmp(list1_version, list2_version) == 1 and list1_arch != list2_arch:
shengjiang.append("最高版本架构不同,无法比较")
elif rpm_vercmp.vercmp(list1_version, list2_version) == -1 and list1_arch == list2_arch:
shengjiang.append("downgrade")
elif rpm_vercmp.vercmp(list1_version, list2_version) == -1 and list1_arch != list2_arch:
shengjiang.append("最高版本架构不同,无法比较")
elif rpm_vercmp.vercmp(list1_version, list2_version) == 0 and list1_arch == list2_arch:
shengjiang.append("nochange")
elif rpm_vercmp.vercmp(list1_version, list2_version) == 0 and list1_arch != list2_arch:
shengjiang.append("最高版本架构不同,无法比较")
print("\n" + labA + "" + labB + "同名软件包升降级判断完成数量:", len(shengjiang))
print(labA + "" + labB + "同名软件包数:", len(tongming), '\n' + labA + "" + labB + "缺失的软件包数:",len(Aqueshi1),"\n" + labB + "" + labA + "缺失的软件包数:", len(Bqueshi1))
return tongming, pkglist1, pkglist2, shengjiang, Aqueshi1, Aqueshi2, Bqueshi1, Bqueshi2, leixing4, leixing5, leixing6, arch4, arch5, arch6
def Genexcel1(A,B,list1,list2,list3,list4,list5,list6,list7,list8,list9,list11,list12,list13,list14,list15,list16,list21,list22,list23,list24,list25,list26,list27,list28,list31,list32,list33,list34,srclist0,srclist1,srclist2,srclist11,srclist21):
now = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') #当前时间
fileName = A+"_"+B+"-"+u'%s-%s.xlsx' % ("仓库对比测试(郭超)",now)
#fileName = "仓库对比-identity_new1.xls" #
output_dir = "test_results/" + fileName
workbook = xlsxwriter.Workbook(output_dir) # 创建工作簿
# 新增一个粗体格式
bold = workbook.add_format({'bold': True})
# 设置自动换行
column_text_wrap = workbook.add_format()
column_text_wrap.set_text_wrap()
# 创建子表
worksheet4 = workbook.add_worksheet("结果汇总")
worksheet1 = workbook.add_worksheet("软件包差异情况对比-严格")
worksheet2 = workbook.add_worksheet("同名软件包版本变化情况对比-宽松")
worksheet3 = workbook.add_worksheet("软件包缺失情况-宽松")
worksheet5 = workbook.add_worksheet("软件包增多情况-宽松")
#data1=[A+'较'+B+'多的软件包',A+'较'+B+'少的软件包',A+'较'+B+'少的软件包对应源码包',A+'较'+B+'相同的软件包','相同软件包类型']
data1 = [A + '' + B + '多的软件包','多的软件包对应类型' ,'多的软件包对应架构' ,A + '' + B + '缺少的软件包', '缺少的软件包对应类型','缺少的软件包对应架构', A + '' + B + '相同的软件包',
'相同的软件包对应类型','相同的软件包对应架构']
data2=[A+""+B+"同名的软件包",A+"中的软件包版本",B+"中的软件包版本",A+"相对"+ B+"的升降级情况","对应类型","对应架构",A+"中对应源码包"]
data3=[A+""+B+"缺失的软件包名",A+""+B+"缺失的软件包","缺失的软件包对应类型","缺失的软件包对应架构","对应源码包","对应源码包(去重)"]
data4=["汇总项","总数量",'模块包数量','非模块包数量']
data5=[A+""+B+"增多的软件包名",A+""+B+"增多的软件包","增多的软件包对应类型","增多的软件包对应架构","对应源码包","对应源码包(去重)"]
worksheet1.write_row('A1',data1,bold)
worksheet2.write_row('A1', data2, bold)
worksheet3.write_row('A1', data3, bold)
worksheet4.write_row('A1', data4, bold)
worksheet5.write_row('A1', data5, bold)
col = ord('A')
worksheet1.write_column(chr(col) + "2", list1)
worksheet1.write_column(chr(col + 1) + "2", list2)
worksheet1.write_column(chr(col + 2) + "2", list3)
worksheet1.write_column(chr(col + 3) + "2", list4)
worksheet1.write_column(chr(col + 4) + "2", list5)
worksheet1.write_column(chr(col + 5) + "2", list6)
worksheet1.write_column(chr(col + 6) + "2", list7)
worksheet1.write_column(chr(col + 7) + "2", list8)
worksheet1.write_column(chr(col + 8) + "2", list9)
worksheet2.write_column(chr(col) + "2", list11)
worksheet2.write_column(chr(col + 1) + "2", list12)
worksheet2.write_column(chr(col + 2) + "2", list13)
worksheet2.write_column(chr(col + 3) + "2", list14)
worksheet2.write_column(chr(col + 4) + "2", list15)
worksheet2.write_column(chr(col + 5) + "2", list16)
worksheet2.write_column(chr(col + 6) + "2", srclist0)
worksheet3.write_column(chr(col) + "2", list21)
worksheet3.write_column(chr(col + 1) + "2", list22)
worksheet3.write_column(chr(col + 2) + "2", list23)
worksheet3.write_column(chr(col + 3) + "2", list24)
worksheet3.write_column(chr(col + 4) + "2", srclist1)
worksheet3.write_column(chr(col + 5) + "2", srclist11)
worksheet5.write_column(chr(col ) + "2", list25)
worksheet5.write_column(chr(col + 1) + "2", list26)
worksheet5.write_column(chr(col + 2) + "2", list27)
worksheet5.write_column(chr(col + 3) + "2", list28)
worksheet5.write_column(chr(col + 4) + "2", srclist2)
worksheet5.write_column(chr(col + 5) + "2", srclist21)
worksheet4.write_column(chr(col) + "2", list31)
worksheet4.write_column(chr(col + 1) + "2", list32)
worksheet4.write_column(chr(col + 2) + "2", list33)
worksheet4.write_column(chr(col + 3) + "2", list34)
workbook.close()
return output_dir
def main(labA,labB,data1,data2,filename1,filename2):
""""i686 x86_64 aarch64 ,module 非module 需要区分开,分别进行比较
首先对仓库软件包架构进行区分不同架构的包又分为module 以及非moule再进行比较
例如 i686 x86_64 aarch64 noarch in name ,只对 i686进行区分
"""
#print(Openfile(labA,data1))
#LISTA1A,Mbs1A,Fmbs1A,LISTA2A,Mbs2A,Fmbs2A,LISTA3A,Mbs3A,Fmbs3A=rjbgfh(Openfile(labA,data1))
#LISTA1B,Mbs1B,Fmbs1B,LISTA2B,Mbs2B,Fmbs2B,LISTA3B,Mbs3B,Fmbs3B=rjbgfh(Openfile(labB,data2))
LISTA1A, Mbs1A, Fmbs1A, LISTA2A, Mbs2A, Fmbs2A, LISTA3A, Mbs3A, Fmbs3A = rjbfx(labA,data1)
LISTA1B, Mbs1B, Fmbs1B, LISTA2B, Mbs2B, Fmbs2B, LISTA3B, Mbs3B, Fmbs3B = rjbfx(labB,data2)
Aduo, leixing3, Ashao, leixing2, ABtong, leixing1,arch1,arch2,arch3,mk1,fmk1,mk2,fmk2,mk3,fmk3= comp1(labA, labB, LISTA1A,LISTA1B)
print('\n' + "##### 03 i686 模块 宽松模式(按包名统计) 软件包对比开始 #####")
Atongming, Apkglist1, Apkglist2, Ashengjiang,AAqueshi1,AAqueshi2,ABqueshi1,ABqueshi2,Aleixing4,Aleixing5,Aleixing6,Aarch4,Aarch5,Aarch6=comp2(labA+ ' 模块',labB+ ' 模块',Mbs2A,Mbs2B)
print('\n' + "##### 04 i686 非模块 宽松模式(按包名统计) 软件包对比开始 #####")
Btongming, Bpkglist1, Bpkglist2, Bshengjiang, BAqueshi1, BAqueshi2, BBqueshi1, BBqueshi2, Bleixing4, Bleixing5, Bleixing6 ,Barch4,Barch5,Barch6= comp2(labA + ' 非模块', labB + ' 非模块', Fmbs2A, Fmbs2B)
print('\n' + "##### 03 非i686 模块 宽松模式(按包名统计) 软件包对比开始 #####")
Ctongming, Cpkglist1, Cpkglist2, Cshengjiang,CAqueshi1,CAqueshi2,CBqueshi1,CBqueshi2,Cleixing4,Cleixing5,Cleixing6,Carch4,Carch5,Carch6=comp2(labA+ ' 模块',labB+ ' 模块',Mbs3A,Mbs3B)
print('\n' + "##### 04 非i686 非模块 宽松模式(按包名统计) 软件包对比开始 #####")
Dtongming, Dpkglist1, Dpkglist2, Dshengjiang, DAqueshi1, DAqueshi2, DBqueshi1, DBqueshi2, Dleixing4, Dleixing5, Dleixing6,Darch4,Darch5,Darch6 = comp2(labA + ' 非模块', labB + ' 非模块', Fmbs3A, Fmbs3B)
tongming=Atongming+Btongming+Ctongming+Dtongming
pkglist1=Apkglist1+Bpkglist1+Cpkglist1+Dpkglist1
pkglist2=Apkglist2+Bpkglist2+Cpkglist2+Dpkglist2
shengjiang=Ashengjiang+Bshengjiang+Cshengjiang+Dshengjiang
Aqueshi1=AAqueshi1+BAqueshi1+CAqueshi1+DAqueshi1 #包名
Aqueshi2=AAqueshi2+BAqueshi2+CAqueshi2+DAqueshi2 #包全名
Bqueshi1=ABqueshi1+BBqueshi1+CBqueshi1+DBqueshi1
Bqueshi2=ABqueshi2+BBqueshi2+CBqueshi2+DBqueshi2
leixing4=Aleixing4+Bleixing4+Cleixing4+Dleixing4
leixing5=Aleixing5+Bleixing5+Cleixing5+Dleixing5
leixing6=Aleixing6+Bleixing6+Cleixing6+Dleixing6
arch4=Aarch4+Barch4+Carch4+Darch4
arch5=Aarch5+Barch5+Carch5+Darch5
arch6=Aarch6+Barch6+Carch6+Darch6
srclist0=getsrc(pkglist1, filename1)
srclist1=getsrc(Aqueshi2, filename2)
srclist11 = list(set(srclist1))
srclist2=getsrc(Bqueshi2, filename1)
srclist21 = list(set(srclist2))
huizong=[labA+'获取到软件包:','',labB+'获取到软件包:','','严格模式'+labA + "" + labB + "同名软件包:",'严格模式'+labA+"缺失软件包:",'严格模式'+labA+"增加软件包:",'','宽松模式'+labA + "" + labB + "同名软件包:",'宽松模式'+labA+"缺失软件包:",'宽松模式'+labA+"增加软件包:"]
huizong1=[len(LISTA1A),'',len(LISTA1B),'',len(ABtong),len(Ashao),len(Aduo),'',len(tongming),len(Aqueshi1),len(Bqueshi1)]
huizong2=[len(Mbs1A),'',len(Mbs1B),'',mk1,mk2,mk3,'',len(Atongming)+len(Ctongming),len(AAqueshi1)+len(CAqueshi1),len(ABqueshi1)+len(CBqueshi1)]
huizong3=[len(Fmbs1A),'',len(Fmbs1B),'',fmk1,fmk2,fmk3,'',len(Btongming)+len(Dtongming),len(BAqueshi1)+len(DAqueshi1),len(BBqueshi1)+len(DBqueshi1)]
resultname=Genexcel1(labA, labB, Aduo, leixing3,arch3, Ashao, leixing2,arch2,ABtong, leixing1,arch1,tongming, pkglist1, pkglist2, shengjiang,
leixing4,arch6, Aqueshi1, Aqueshi2, leixing5,arch4,Bqueshi1, Bqueshi2, leixing6,arch5,huizong,huizong1,huizong2,huizong3,srclist0,srclist1,srclist2,srclist11,srclist21)
# return labA, labB, Aduo, leixing3,arch3, Ashao, leixing2,arch2,ABtong, leixing1,arch1,tongming, pkglist1, pkglist2, shengjiang,arch6,leixing4, Aqueshi1, Aqueshi2, leixing5,arch4,Bqueshi1, Bqueshi2, leixing6,arch5,huizong,huizong1,huizong2,huizong3
return resultname
from bs4 import BeautifulSoup
#用于从网页获取软件包列表
def Getpkglist(url):
head = {"User-Agent": "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122 Safari / 537.36"}
# 查找所有<a>标签
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
PkgList = []
packages = []
directories = []
for a in soup.find_all('a', href=True):
href = a['href']
if href.endswith('/'):
# 如果链接以斜杠结尾,表示是一个目录
directories.append(href)
elif href.endswith('.rpm'):
# 如果链接以.rpm结尾表示是一个软件包
packages.append(href)
print(directories)
directory_urls=[]
for directory in directories:
directory_url= '/'.join([url, directory])
directory_urls.append(directory_url)
print(directory_urls)
for baseurl in directory_urls:
response = requests.get(baseurl, headers=head)
html = response.text
html = etree.HTML(html)
a_links = html.xpath("//a") # 查找所有 a 标签 ,//a 代表 html 下所有 a,html/body/a 同理
for item in a_links:
item1 = item.get('href') # 获取a标签某一属性
if ".rpm" in item1: # 去除非rpm包的影响备选.el8
item1 = item1.replace("%2B", "+") # 对url不能识别”+“进行修正
PkgList.append(item1)
print('\n'+'##### 00 通过 Gethtml 获取软件包列表完成,软件包数量:',len(PkgList),'#####') #检修口
return PkgList
# repo1=("https://update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/BaseOS/x86_64/","https://update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/AppStream/x86_64/","https://update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/PowerTools/x86_64/")
# repo2=("http://192.168.114.103/update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/BaseOS/x86_64/","http://192.168.114.103/update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/AppStream/x86_64/","http://192.168.114.103/update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/PowerTools/x86_64/")
# data1,filename1=plus("https://10.44.16.185/private_test/history/repo/20231217/pub/V7/V7Update9/os/adv/lic/base/x86_64/ ")#,"https://update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/AppStream/x86_64/")
# data2,filename2=plus("http://192.168.114.103/update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/base/x86_64/")#,"http://192.168.114.103/update.cs2c.com.cn/NS/V10/8U4/os/adv/lic/AppStream/x86_64/")
#data2,filename2=plus("https://10.44.16.185/NS/V10/8U6/os/adv/lic/BaseOS-updates/x86_64/os/","https://10.44.16.185/NS/V10/8U6/os/adv/lic/AppStream-updates/x86_64/os/")
#data2,filename2=plus("http://10.44.34.103/ns8.4/compose/BaseOS-updates/x86_64/os/","http://10.44.34.103/ns8.4/compose/AppStream-updates/x86_64/os/")
# data2,filename2=plus("https://dl.fedoraproject.org/pub/epel/8/Everything/x86_64/")
#
# #Baseos
# data1,filename1=plus("https://eps-server.openkylin.top/NS/V10/V10SP3/EPKL/main/x86_64/")
# data2,filename2=plus("https://10.44.16.185/private_test/history/repo/20231217/pub/V7/V7Update9/os/adv/lic/base/x86_64/","https://10.44.16.185/private_test/history/repo/20231217/pub/V7/V7Update9/os/adv/lic/base/i686/")
# https://eps-server.openkylin.top/NS/V10/V10SP3/EPKL/main/x86_64/
# data1,filename1=plus("http://192.168.114.104/sp3-epkl-updates-3/source/")
# data1,filename1=plus("http://192.168.114.104/sp3-epkl-base/","http://192.168.114.104/sp3-epkl-updates-5/source/","http://192.168.114.104/sp3-epkl-220x/source/","http://192.168.114.104/sp3-epkl-230x/source/")
# data1,filename1=plus("http://192.168.114.104/sp1-epkl-base/","http://192.168.114.104/sp3-epkl-updates-3/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-24.03-LTS/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-24.09/EPOL/main/source/")
# main("epkl-base+updates+22.0x+23.0x","epol-24.0x",data1,data2,filename1,filename2)
# data2,filename2=plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP1/EPOL/source/","https://repo.openeuler.org/openEuler-20.03-LTS-SP2/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP4/EPOL/main/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP1/EPOL/update/source/","https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP2/EPOL/update/main/source/","https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/EPOL/update/main/source/","https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP4/EPOL/update/main/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP1/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP2/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP3/EPOL/main/source/","https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP4/EPOL/main/source/")
#epol 仓库地址
# data2, filename2 = plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP1/EPOL/source/")
# data2, filename2 = plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP2/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP4/EPOL/main/source/")
# data2, filename2 = plus("https://archives.openeuler.openatom.cn/openEuler-21.03/EPOL/source/")
# data2, filename2 = plus("https://archives.openeuler.openatom.cn/openEuler-21.09/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP1/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP2/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP3/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP4/EPOL/main/source/")
# data2, filename2 = plus("https://archives.openeuler.openatom.cn/openEuler-22.09/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-23.03/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-23.09/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-24.03-LTS/EPOL/main/source/")
# data2, filename2 = plus("https://repo.openeuler.openatom.cn/openEuler-24.09/EPOL/main/source/")
# data1,filename1=plus("http://192.168.114.104/sp3-epkl-base/","http://192.168.114.104/sp3-epkl-updates-4/source/")
# data2, filename2 = plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP1/EPOL/source/")
# data3, filename3 = plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP3/EPOL/main/source/")
# data4, filename4 = plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/EPOL/main/source/")
# data5, filename5 = plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP4/EPOL/main/source/")
# #21
# data6, filename6 = plus("https://archives.openeuler.openatom.cn/openEuler-21.03/EPOL/source/")
# data7, filename7 = plus("https://archives.openeuler.openatom.cn/openEuler-21.09/EPOL/main/source/")
# #22
# data8, filename8 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS/EPOL/main/source/")
# data9, filename9 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP1/EPOL/main/source/")
# data10, filename10 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP2/EPOL/main/source/")
# data11, filename11 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP3/EPOL/main/source/")
# data12, filename12 = plus("https://repo.openeuler.openatom.cn/openEuler-22.03-LTS-SP4/EPOL/main/source/")
# data13, filename13 = plus("https://archives.openeuler.openatom.cn/openEuler-22.09/EPOL/main/source/")
#23
# data14, filename14 = plus("https://repo.openeuler.openatom.cn/openEuler-23.03/EPOL/main/source/")
# data15, filename15 = plus("https://repo.openeuler.openatom.cn/openEuler-23.09/EPOL/main/source/")
#24
# data16, filename16 = plus("https://repo.openeuler.openatom.cn/openEuler-24.03-LTS/EPOL/main/source/")
# data17, filename17 = plus("https://repo.openeuler.openatom.cn/openEuler-24.09/EPOL/main/source/")
#oepkg
# data18, filename18 = plus('https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS/extras/source/',"https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP1/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP2/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP3/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP4/extras/source/")
# data19, filename19 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP1/extras/source/")
# data20, filename20 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP2/extras/source/")
# data21, filename21 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP3/extras/source/")
# data22, filename22 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-20.03-LTS-SP4/extras/source/")
# data23, filename23 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP1/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP2/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP3/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP4/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-24.03-LTS/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-24.09/extras/source/","https://repo.oepkgs.net/openEuler/rpm/openEuler-24.09-LTS/extras/source/")
# data24, filename24 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP1/extras/source/")
# data25, filename25 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP2/extras/source/")
# data26, filename26 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP3/extras/source/")
# data27, filename27 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-22.03-LTS-SP4/extras/source/")
# data28, filename28 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-24.03-LTS/extras/source/")
# data29, filename29 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-24.09/extras/source/")
# data30, filename30 = plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-24.09-LTS/extras/source/")
# main("v10-sp3-epkl-base+updates","20.03-LTS-sp1-epol",data1,data2,filename1,filename2)
# main("v10-sp3-epkl-base+updates","20.03-LTS-sp2-epol",data1,data3,filename1,filename3)
# main("v10-sp3-epkl-base+updates","20.03-LTS-sp3-epol",data1,data4,filename1,filename4)
# main("v10-sp3-epkl-base+updates","20.03-LTS-sp4-epol",data1,data5,filename1,filename5)
#
# main("v10-sp3-epkl-base+updates","21.03-epol",data1,data6,filename1,filename6)
# main("v10-sp3-epkl-base+updates","21.09-epol",data1,data7,filename1,filename7)
#
# main("v10-sp3-epkl-base+updates","22.03-LTS-epol",data1,data8,filename1,filename8)
# main("v10-sp3-epkl-base+updates","22.03-LTS-sp1-epol",data1,data9,filename1,filename9)
# main("v10-sp3-epkl-base+updates","22.03-LTS-sp2-epol",data1,data10,filename1,filename10)
# main("v10-sp3-epkl-base+updates","22.03-LTS-sp3-epol",data1,data11,filename1,filename11)
# main("v10-sp3-epkl-base+updates","22.03-LTS-sp4-epol",data1,data12,filename1,filename12)
# main("v10-sp3-epkl-base+updates","22.09-epol",data1,data13,filename1,filename13)
# main("v10-sp3-epkl-base+updates4","23.03-epol",data1,data14,filename1,filename14)
# main("v10-sp3-epkl-base+updates4","23.09-epol",data1,data15,filename1,filename15)
# main("v10-sp3-epkl-base+updates","24.03-epol",data1,data16,filename1,filename16)
# main("v10-sp3-epkl-base+updates","24.09-epol",data1,data17,filename1,filename17)
# main("v10-sp3-epkl-updates4","oepkg-20.03-LTS-SPX",data1,data23,filename1,filename18)
# main("v10-sp3-epkl-updates4","oepkg-22-24",data1,data23,filename23,filename23)
# main("oe-sp2-source+epol","oepkg-20.03-SP2",data3,data18,filename3,filename18)
#epol updates 仓库
#20.03
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP1/EPOL/update/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP2/EPOL/update/main/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/EPOL/update/main/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP4/EPOL/update/main/source/")
#21.0x
# data2,filename2=plus("https://archives.openeuler.openatom.cn/openEuler-21.03/EPOL/update/source/")
# data2,filename2=plus("https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP4/EPOL/update/main/source/")
# main("v10-epkl-base1+updates","22.03-lts-sp1-epol",data1,data2,filename1,filename2)
# main("v10-epkl-base1+updates","v10-epkl-base",data1,data2,filename1,filename2)
# data2,filename2=plus('https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/33/Server/source/tree/','https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/34/Server/source/tree/','https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/35/Server/source/tree/','https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/36/Server/source/tree/','https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/37/Server/source/tree/','https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/38/Server/source/tree/','https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/39/Server/source/tree/')
# data2,filename2=plus('http://192.168.114.104/fedora/everything/f30/','http://192.168.114.104/fedora/everything/f31/','http://192.168.114.104/fedora/everything/f32/','http://192.168.114.104/fedora/everything/f33/','http://192.168.114.104/fedora/everything/f34/','http://192.168.114.104/fedora/everything/f35/','http://192.168.114.104/fedora/everything/f36/','http://192.168.114.104/fedora/everything/f37/','http://192.168.114.104/fedora/everything/f38/','http://192.168.114.104/fedora/everything/f39/')
# data3,filename3=plus("http://192.168.114.104/fedora/server/f30/")
# data2,filename2=plus("http://192.168.114.104/fedora/everything/f39/")
# main("oepkg-20.0x","fedora-server-30",data18,data3,filename18,filename3)
# main("oepkg-20.0x","fedora-everything-30-39",data18,data2,filename18,filename2)
# data1,filename1=plus("https://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/source/tree/")
# data2,filename2=plus("https://repo.oepkgs.net/openEuler/rpm/openEuler-24.03-LTS/fedora40/source/")
# data1,filename1=plus("http://192.168.114.104/fedora40/")
# data2,filename2=plus("http://192.168.114.104/oe-fedora40/")
# main("fedora40","fedora40",data1,data2,filename1,filename2)
# data1,filename1=plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP3/EPOL/main/source/","https://repo.openeuler.org/openEuler-20.03-LTS-SP3/EPOL/update/main/source/")
# data2,filename2=plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP2/EPOL/main/source/","https://repo.openeuler.org/openEuler-20.03-LTS-SP2/EPOL/update/main/source/")
# data1,filename1=plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP3/EPOL/main/source/","https://repo.openeuler.org/openEuler-20.03-LTS-SP3/EPOL/update/main/source/")
# data2,filename2=plus("https://repo.openeuler.org/openEuler-20.03-LTS-SP4/EPOL/main/source/","https://repo.openeuler.org/openEuler-20.03-LTS-SP4/EPOL/update/main/source/")
#AppStream
#data1,filename1=plus("http://10.44.34.103/ns8.2-new/compose/b09/x86_64/BaseOS/","http://10.44.34.103/ns8.2-new/compose/b09/x86_64/AppStream/","http://10.44.34.103/ns8.2-new/compose/b09/x86_64/PowerTools/")
# data2,filename2=plus("http://192.168.114.15/iso/centos8.2/BaseOS/","http://192.168.114.15/iso/centos8.2/AppStream/")
#zong,
# 仅repodata
# data1,filename1=plus("http://10.44.34.103/ns8.2-new/compose/b09/x86_64/BaseOS/","http://10.44.34.103/ns8.2-new/compose/b09/x86_64/AppStream/","http://10.44.34.103/ns8.2-new/compose/b09/x86_64/PowerTools/")
#data1,filename1=plus("http://10.44.34.103/ns8.2-new/compose/b09/aarch64/BaseOS/","http://10.44.34.103/ns8.2-new/compose/b09/aarch64/AppStream/","http://10.44.34.103/ns8.2-new/compose/b09/aarch64/PowerTools/")
# 全部仓库
# data2,filename2=plus("http://10.44.34.103/ns8.2-new/compose/b17/BaseOS/x86_64/os/","http://10.44.34.103/ns8.2-new/compose/b17/AppStream/x86_64/os/","http://10.44.34.103/ns8.2-new/compose/b17/PowerTools/x86_64/os/")
#data1,filename1=plus("http://10.44.34.103/ns8.2-new/compose/b16/BaseOS/aarch64/os/","http://10.44.34.103/ns8.2-new/compose/b16/AppStream/aarch64/os/","http://10.44.34.103/ns8.2-new/compose/b16/PowerTools/aarch64/os/")
#data1,filename1=plus("http://10.44.34.103/ns8.8/b09/x86_64/BaseOS/","http://10.44.34.103/ns8.8/b09/x86_64/AppStream/","http://10.44.34.103/ns8.8/b09/x86_64/kernel418/")
# data1,filename1=plus("https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/BaseOS/x86_64/os/","https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/AppStream/x86_64/os/","https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/kernel418/x86_64/os/","https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/kernel419/x86_64/os/")
# data2,filename2=plus("https://dl.rockylinux.org/vault/rocky/8.8/BaseOS/x86_64/os/","https://dl.rockylinux.org/vault/rocky/8.8/AppStream/x86_64/os/","https://dl.rockylinux.org/vault/rocky/8.8/PowerTools/x86_64/os/")
# #data1,filename1=plus("https://10.44.16.185/private_test/history/repo/20231226/pub/V10/8U6/os/adv/lic/AppStream-updates/x86_64/os/","https://10.44.16.185/private_test/history/repo/20231226/pub/V10/8U6/os/adv/lic/BaseOS-updates/x86_64/os/")
#data2,filename2=plus("http://192.168.114.15/iso/centos8.2/BaseOS/","http://192.168.114.15/iso/centos8.2/AppStream/","http://192.168.114.15/repo/centos82/powertool/")
# # #data2,filename2=plus("http://192.168.114.15/repo/centos82/baseos/","http://192.168.114.15/repo/centos82/appstrem/","http://192.168.114.15/repo/centos82/powertool/")
#data2,filename2=plus("https://10.44.16.185/NS/V10/8U2/os/adv/lic/BaseOS/x86_64/","https://10.44.16.185/NS/V10/8U2/os/adv/lic/AppStream/x86_64/","https://10.44.16.185/NS/V10/8U2/os/adv/lic/PowerTools/x86_64/")#"https://10.44.16.185/NS/V10/8U2/os/adv/lic/BaseOS-updates/x86_64/","https://10.44.16.185/NS/V10/8U2/os/adv/lic/AppStream-updates/x86_64/","https://10.44.16.185/NS/V10/8U2/os/adv/lic/PowerTools-updates/x86_64/","https://10.44.16.185/NS/V10/8U2/os/adv/lic/Plus-updates/x86_64/")
# 全部仓库
#data2,filename2=plus("http://10.44.34.103/ns8.2-new/compose/b17/BaseOS/x86_64/os/","http://10.44.34.103/ns8.2-new/compose/b17/AppStream/x86_64/os/","http://10.44.34.103/ns8.2-new/compose/b17/PowerTools/x86_64/os/")
#data2,filename2=plus("http://10.44.34.103/ns8.2-new/compose/b17/BaseOS/aarch64/os/","http://10.44.34.103/ns8.2-new/compose/b17/AppStream/aarch64/os/","http://10.44.34.103/ns8.2-new/compose/b17/PowerTools/aarch64/os/")
#data2,filename2=plus("http://10.44.51.209/centos-iso/centos8.2/BaseOS/","http://10.44.51.209/centos-iso/centos8.2/AppStream/","https://172.30.13.199/download/CENTOS8/centos8.2/PowerTools/aarch64/os/")
#data2,filename2=plus("https://10.44.16.185/private_test/repo/V10/8U4/os/adv/lic/AppStream-updates/x86_64/","https://10.44.16.185/private_test/repo/V10/8U4/os/adv/lic/BaseOS-updates/x86_64/")
# data1,filename1=plus("https://10.44.16.185/private_test/repo/V10/8U8/Build06/os/adv/lic/AppStream/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build06/os/adv/lic/BaseOS/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build06/os/adv/lic/kernel418/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build06/os/adv/lic/kernel419/x86_64/os/")
# #data2,filename2=plus("https://10.44.16.185/private_test/repo/V10/8U8/Build04/os/adv/lic/AppStream/aarch64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build04/os/adv/lic/BaseOS/aarch64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build04/os/adv/lic/kernel418/aarch64/os/")
# data1,filename1=plus("https://10.44.16.185/private_test/repo/V10/8U8/Build06/os/adv/lic/BaseOS-updates/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build06/os/adv/lic/AppStream-updates/x86_64/os/")
# data2,filename2=plus("https://10.44.16.185/private_test/repo/V10/8U8/Build05/os/adv/lic/BaseOS-updates/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build05/os/adv/lic/AppStream-updates/x86_64/os/")
# data2,filename2=plus("https://10.44.16.185/private_test/repo/V10/8U8/Build05/os/adv/lic/AppStream/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build05/os/adv/lic/BaseOS/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build05/os/adv/lic/kernel418/x86_64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build05/os/adv/lic/kernel419/x86_64/os/")
# # data2,filename2=plus("https://10.44.16.185/private_test/repo/V10/8U8/Build03/os/adv/lic/AppStream/aarch64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build03/os/adv/lic/BaseOS/aarch64/os/","https://10.44.16.185/private_test/repo/V10/8U8/Build03/os/adv/lic/kernel418/aarch64/os/")
# pkglist=['nginx-filesystem-1.14.1-9.module_el8.6.0+647+66e644f1.ks8.01.noarch.rpm nginx-filesystem-1.16.1-2.module_el8.6.0+648+f70baf93.1.ks8.01.noarch.rpm nginx-filesystem-1.18.0-3.module_el8.6.0+649+52b76903.1.ks8.01.noarch.rpm nginx-filesystem-1.20.1-1.module_el8.6.0+707+e975214f.noarch.rpm nginx-filesystem-1.22.1-1.module_el8.8.0+948+03a664b8.noarch.rpm','cesi-fonts-1.0.0-1.el8.noarch.rpm']
# print(getsrc(pkglist,filename1))
#data2=gethtml("http://10.44.51.209/guochao/rhel8.8/x86_64/AppStream/Packages/","http://10.44.51.209/guochao/rhel8.8/x86_64/BaseOS/Packages/")
#data2=gethtml("http://10.44.51.209/guochao/rhel8.8/aarch64/AppStream/Packages/","http://10.44.51.209/guochao/rhel8.8/aarch64/BaseOS/Packages/")
#data2=openfile('C:/Users/lenovo.DESKTOP-JAA702O/Desktop/123')
#data2=gethtml("https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.2.2004/AppStream/x86_64/os/Packages/","https://mirrors.tuna.tsinghua.edu.cn//centos-vault/8.2.2004/BaseOS/x86_64/os/Packages/","https://mirrors.tuna.tsinghua.edu.cn/centos-vault/8.2.2004/PowerTools/x86_64/os/Packages/")
#data2,filename2=plus("https://10.44.16.185/NS/V10/8U8/os/adv/lic/AppStream/x86_64/os/","https://10.44.16.185/NS/V10/8U8/os/adv/lic/BaseOS/x86_64/os/")
#data2,filename2=plus("https://10.44.16.185/NS/V10/8U8/os/adv/lic/AppStream/aarch64/os/","https://10.44.16.185/NS/V10/8U8/os/adv/lic/BaseOS/aarch64/os/")
# # data1,filename1=plus("https://update.cs2c.com.cn/NS/V10/8U8/os/adv/lic/AppStream/x86_64/os/")#e.cs2c.com.cn/NS/V10/8U4/os/adv/lic/AppStream/x86_64/")
# data1=gethtml("http://192.168.114.206/iso/rh8.10src/AppStream/Packages/")#,
# main("rhel8.10-app-src","ns8.8-app-src",data1,data2,filename1,filename2)
# main("185-6.5","103-6.5",data1,data2,filename1,filename2)
# main("v10-sp3-epel","openeuler-20.03-sp4-epol",data1,data2,filename1,filename2)
# main("v10-sp3-epel","centos8-epel",data1,data2,filename1,filename2)
# main("ns7.9-0206","ns7.9-1217",data1,data2,filename1,filename2)
# main("ns8.4-updates-103","ns8.4-185",data1,data2,filename1,filename2)
# main("ns8.2-b01-arm-zong","centos8.2",data1,data2,filename1,filename2)
# data2=gethtml("https://172.30.13.199/download/epel/epel/7/x86_64/Packages/")
# filename2=""
# ###文件对比###
# data1=openfile("Kylin-Linux-Advanced-Server-v10-Release-Build06.12.06-lic-zj-package.txt")
# #data2=openfile('C:/Users/lenovo.DESKTOP-JAA702O/Desktop/ns8.2-baseos-ww.txt','C:/Users/lenovo.DESKTOP-JAA702O/Desktop/ns8.2-app-ww.txt')
# data2=openfile("nsV7Update6-adv-lic-build4-package.txt")
# filename1=""
# filename2=""
# main("v10-zj","v7.6",data1,data2,filename1,filename2)
# data1,filename1=plus("https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/base/x86_64/","https://update.cs2c.com.cn/NS/V7/V7Update9/os/adv/lic/updates/x86_64/")
# data1,filename1=plus("http://192.168.114.102/v10.1/base/","http://192.168.114.102/v10.1/kernel/")
# data2=gethtml("https://vault.centos.org/7.9.2009/os/x86_64/Packages/","https://vault.centos.org/7.9.2009/updates/x86_64/Packages/")
# # main("ns8.10-061420-src","0619",data1,data2,filename1,filename2)
# filename2=""
# main("v10.1-cs2c","centos7.9-base+updates",data1,data2,filename1,filename2)
"""
过于繁琐
函数改为自动判断、选择
鲁棒性,容错性太低
log输出
结果输出格式需要易用性高
"""

View File

@@ -0,0 +1,453 @@
import bz2
import lzma
import shutil
import sqlite3
import requests
from lxml import etree
import logging
logging.captureWarnings(True)
import gzip
import os
# def Getsqlite(baseurl):
# """
# 从repodata中获取数据库压缩文件下载并解压
# url 格式 例如 basrutl=https://update.cs2c.com.cn/NS/V10/8U6/os/adv/lic/AppStream/x86_64/os/
# """
# head = {
# "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
# }
# path = f"{baseurl}repodata/"
# try:
# response = requests.get(path, headers=head, verify=False)
# html = response.text
# html = etree.HTML(html)
# a_links = html.xpath("//a")
#
# for item in a_links:
# item1 = item.get('href')
# if item1.endswith("primary.sqlite.bz2"):
# sqlite_url = '/'.join([path, item1])
# print(sqlite_url)
# response = requests.get(sqlite_url, headers=head, verify=False)
# un_path = "repotest.sqlite.bz2"
# with open(un_path, 'wb') as code:
# code.write(response.content)
# bz2file = bz2.BZ2File(un_path)
# data = bz2file.read()
# newfilepath = "repotest.sqlite"
# open(newfilepath, 'wb').write(data)
# return newfilepath
#
# elif item1.endswith("primary.sqlite.xz"):
# sqlite_url = '/'.join([path, item1])
# print(sqlite_url)
# response = requests.get(sqlite_url, headers=head, verify=False)
# un_path = "repotest.sqlite.xz"
#
# with open(un_path, 'wb') as code:
# code.write(response.content)
#
# with lzma.open(un_path, 'rb') as input:
# with open("repotest.sqlite", 'wb') as output:
# shutil.copyfileobj(input, output)
# return "repotest.sqlite"
#
# elif item1.endswith("primary.sqlite.gz"):
# sqlite_url = '/'.join([path, item1])
# print(sqlite_url)
# response = requests.get(sqlite_url, headers=head, verify=False)
# un_path = "repotest.sqlite.gz"
#
# with open(un_path, 'wb') as code:
# code.write(response.content)
#
# with gzip.open(un_path, 'rb') as input:
# with open("repotest.sqlite", 'wb') as output:
# shutil.copyfileobj(input, output)
# return "repotest.sqlite"
#
# print("获取数据库文件失败,请检查!")
# return None
#
# except Exception as e:
# print("发生异常:", e)
# return None
def Getsqlite(baseurl):
"""
从仓库 URL 下载并解压 repodata.sqlite 文件
"""
import os # 确保在函数内导入(如果全局未导入)
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
try:
# 确保 baseurl 以 '/' 结尾
if not baseurl.endswith('/'):
baseurl += '/'
# 正确拼接 repodata 路径
repodata_url = f"{baseurl}repodata/"
response = requests.get(repodata_url, headers=head, verify=False)
response.raise_for_status() # 确保请求成功
html = etree.HTML(response.text)
a_links = html.xpath("//a")
# 支持的压缩格式
supported_formats = [
("primary.sqlite.bz2", bz2.BZ2File),
("primary.sqlite.xz", lzma.open),
("primary.sqlite.gz", gzip.open)
]
for item in a_links:
href = item.get('href', '')
for suffix, opener in supported_formats:
if href.endswith(suffix):
# 正确拼接下载 URL
sqlite_url = f"{repodata_url}{href}"
print(f"下载数据库文件: {sqlite_url}")
# 下载压缩文件
response = requests.get(sqlite_url, headers=head, verify=False)
response.raise_for_status()
# 生成临时压缩文件路径
temp_compressed = f"repotest.sqlite.{suffix.split('.')[-1]}"
with open(temp_compressed, 'wb') as f:
f.write(response.content)
# 解压文件
newfilepath = "repotest.sqlite"
with opener(temp_compressed, 'rb') as compressed_file:
with open(newfilepath, 'wb') as decompressed_file:
shutil.copyfileobj(compressed_file, decompressed_file)
# 验证文件是否存在
if not os.path.isfile(newfilepath):
raise FileNotFoundError(f"解压失败: {newfilepath} 不存在")
return newfilepath # 返回有效路径
raise ValueError(f"未找到支持的数据库文件: {repodata_url}")
except Exception as e:
print(f"获取数据库文件失败: {e}")
raise # 抛出异常供上层处理
def Getsqlite1(baseurl):
"""
从repodata中获取数据库压缩文件下载并解压
url 格式 例如 basrutl=https://update.cs2c.com.cn/NS/V10/8U6/os/adv/lic/AppStream/x86_64/os/
"""
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
path = f"{baseurl}repodata"
response = requests.get(path, headers=head,verify=False)
#print(response)
html = response.text
#print(html)
html = etree.HTML(html)
a_links = html.xpath("//a") # 查找所有 a 标签 ,//a 代表 html 下所有 a,html/body/a 同理
for item in a_links:
item1 = item.get('href') # 获取a标签某一属性
if "primary.sqlite.bz2" or "primary.sqlite.xz" in item1:
sqlite_url = '/'.join([path, item1])
#sqlite_url=f"{path}/{item1}"# 其他方法
response = requests.get(sqlite_url,headers=head,verify=False)
un_path = "repotest.sqlite"
if "primary.sqlite.bz2" in item1:#识别bz2压缩文件
print(sqlite_url)
bz2path = 'repotest.sqlite.bz2'
#un_path = "repotest.sqlite"
with open(bz2path, 'wb') as code:
code.write(response.content)
bz2file = bz2.BZ2File(bz2path)
data = bz2file.read()
newfilepath = un_path
open(newfilepath, 'wb').write(data)
elif "primary.sqlite.xz" in item1: #识别xz压缩文件
print(sqlite_url)
xzfile = 'repotest.sqlite.xz'
#un_path = 'repotest.sqlite'
with open(xzfile, 'wb') as code:
code.write(response.content)
with lzma.open(xzfile, 'rb') as input:
with open(un_path, 'wb') as output:
shutil.copyfileobj(input, output)
else:
print("获取数据库文件失败,请检查!")
return un_path
# def Getrpminfo(database):
# """
# 连接sqlite数据库获取软件包信息
# """
# con = sqlite3.connect(database)
# ##读取sqlite数据
# cursor = con.cursor()
# ##创建游标cursor来执行execute语句
# cursor.execute("SELECT * from packages")
# # packs = cursor.fetchall() # 获取整个列表数据
# content = cursor.execute("SELECT name, version, epoch, release, arch, rpm_sourcerpm, pkgId from packages")
# rpmname=[]
# filename1 = []
# for row in cursor:
# rpm=row[0] + "-" + row[1] + "-" + row[3] + "." + row[4] + ".rpm"
# srcname=row[5].rsplit('-',2)[0]
# #rpmname.append(row[0] + "-" + row[1] + "-" + row[3] + "." + row[4] + ".rpm")
# #(n, v, e, r, a, s, i) = (row[0], row[1], row[2], row[3], row[4], row[5], row[6])
# # filename = {'rpmname': rpm, 'name': row[0], 'version': row[1], 'epoch': row[2], 'release': row[3], 'arch': row[4], 'sourcerpm': row[5], 'pkgId': row[6]}
# filename = {'rpmname':rpm,'name':row[0], 'version': row[1], 'epoch':row[2],'release': row[3], 'arch': row[4],'sourcerpm':row[5],'pkgId': row[6],'srcname': srcname}
# filename1.append(filename)
# #print('\n' + '##### 通过 Getrpminfo 获取软件包 获取软件包nvr', len(filename1)) # 检修口
# #print(len(PkgList), filename2)
# return filename1
# cursor.close()
# con.close()
def Getrpminfo(database):
"""
连接sqlite数据库获取软件包信息
"""
if not os.path.isfile(database):
raise FileNotFoundError(f"数据库文件不存在: {database}")
try:
con = sqlite3.connect(database)
cursor = con.cursor()
cursor.execute("SELECT name, version, epoch, release, arch, rpm_sourcerpm, pkgId from packages")
filename1 = []
for row in cursor:
srcname = row[5].rsplit('-', 2)[0]
filename = {
'rpmname': f"{row[0]}-{row[1]}-{row[3]}.{row[4]}.rpm",
'name': row[0],
'version': row[1],
'epoch': row[2],
'release': row[3],
'arch': row[4],
'sourcerpm': row[5],
'pkgId': row[6],
'srcname': srcname
}
filename1.append(filename)
return filename1
except sqlite3.Error as e:
print(f"数据库操作失败: {e}")
return []
finally:
if 'con' in locals():
con.close()
# def plus(*baseurls):
# """
# 用于处理多个url,合并处理结果
# """
# filename2=[]
# PkgList=[]
# for baseurl in baseurls:
# un_path=Getsqlite(baseurl)
# filename1=Getrpminfo(un_path)
# filename2.extend(filename1)
# for filenme in filename2:
# PkgList.append(filenme["rpmname"])
# print('\n' + '##### 通过 Getrpminfo , 获取软件包nvr 完成:', len(filename2)) # 检修口
# return PkgList,filename2
# def plus(*baseurls):
# """
# 用于处理多个url,合并处理结果
# """
# filename2 = []
# PkgList = []
# for baseurl in baseurls:
# try:
# un_path = Getsqlite(baseurl)
# filename1 = Getrpminfo(un_path)
# filename2.extend(filename1)
# except Exception as e:
# print(f"处理仓库 {baseurl} 失败: {e}")
# continue # 跳过当前仓库,继续处理下一个
#
# for filenme in filename2:
# PkgList.append(filenme["rpmname"])
#
# print(f"成功处理 {len(filename2)} 个软件包")
# return PkgList, filename2
def plus(*baseurls):
"""
处理多个仓库 URL合并结果
"""
filename2 = []
PkgList = []
for baseurl in baseurls:
try:
un_path = Getsqlite(baseurl)
filename1 = Getrpminfo(un_path)
filename2.extend(filename1)
except Exception as e:
print(f"跳过仓库 {baseurl}: {str(e)}")
continue
for filenme in filename2:
PkgList.append(filenme["rpmname"])
print(f"成功处理 {len(filename2)} 个软件包")
return PkgList, filename2
def plus1(url):
"""
用于处理多个url,合并处理结果
"""
filename2=[]
PkgList=[]
baseurls=url.split(',')
for baseurl in baseurls:
un_path=Getsqlite(baseurl)
# filename1=Getrpminfo(un_path)
filename1 = Getrpminfo(un_path)
filename2.extend(filename1)
for filenme in filename2:
PkgList.append(filenme["rpmname"])
print('\n' + '##### 通过 Getrpminfo , 获取软件包nvr 完成:', len(filename2)) # 检修口
return PkgList,filename2
def getsrc(pkglist,filename):
"""
用于获取对应软件包的源码包名称name为全名
"""
srclist=[]
pkgidlist = []
for pkg in pkglist:
pkg=list(pkg.split())
for i in filename:
if pkg[-1]==i["rpmname"]:
srclist.append(i["sourcerpm"]) ####获取其他标签###
# pkgidlist.append(i["pkgId"]) ####获取其他标签###
#srclist = list(set(srclist))
return srclist
def getsrc1(pkglist,filename):
"""
用于获取对应软件包的源码包名称name为全名
"""
srclist=[]
pkgidlist = []
for pkg in pkglist:
pkg=list(pkg.split())
for i in filename:
if pkg[-1]==i["rpmname"]:
srclist.append(i["sourcerpm"]) ####获取其他标签###
pkgidlist.append(i["pkgId"]) ####获取其他标签###
#srclist = list(set(srclist))
return pkglist,pkgidlist,srclist
def getsrc2(pkglist,filename):
"""
用于获取对应软件包的源码包名称name为全名
"""
srclist=[]
srcnamelist = []
for pkg in pkglist:
pkg=list(pkg.split())
for i in filename:
if pkg[-1]==i["rpmname"]:
srclist.append(i["sourcerpm"]) ####获取其他标签###
srcnamelist.append(i["srcname"]) ####获取其他标签###
#srclist = list(set(srclist))
return srclist,srcnamelist
def getpkgid(pkglist,filename):
"""
用于获取对应软件包的 pkgidname为全名
"""
pkgidlist=[]
for pkg in pkglist:
#pkg=list(pkg.split())
for i in filename:
if pkg==i["rpmname"]:
pkgidlist.append(i["pkgId"]) ####获取其他标签###
#srclist = list(set(srclist))
#print(pkgidlist)
return pkgidlist
def pkgiddiff(pkglist,filename1,filename2):
result1=[]
for pkg in pkglist:
for i in filename1:
if i["rpmname"]==pkg:
pkgId1=i["pkgId"]
for J in filename2:
if J["rpmname"] == pkg:
pkgId2 = J["pkgId"]
if pkgId1 == pkgId2:
result1.append("一致")
else:
result1.append("不一致")
return result1
#方法3.0保存结果生成txt
def GenTxT(Sourcerpm):
filename = input("please input your filename for generate result: ")
f=open("./"+filename+".txt", "w", encoding="utf-8")
for pkg in Sourcerpm:
f.write(str(pkg+'\n'))
f.close
import pandas as pd
def write_to_csv(pkglist, pkgidlist, srclist, filename='output.csv'):
"""
将 pkglist、pkgidlist 和 srclist 写入 CSV 文件。
参数:
pkglist : list
软件包全名的列表。
pkgidlist : list
包 ID 的列表。
srclist : list
源码包名称的列表。
filename : str
输出 CSV 文件的名称。
"""
# 创建一个 DataFrame
data = {
'Package Name': pkglist,
'Package ID': pkgidlist,
'Source RPM': srclist
}
df = pd.DataFrame(data)
# 将 DataFrame 写入 CSV 文件
df.to_csv(filename, index=False, encoding='utf-8-sig')
#
# pkglist,filename=plus("https://update.cs2c.com.cn/NS/V10/V10SP3-2403/os/adv/lic/updates/x86_64/")
# print(pkglist,filename)
# pkglist, pkgidlist, srclist=getsrc1(pkglist,filename)
# write_to_csv(pkglist, pkgidlist, srclist, filename='sp3-loongarch-output.csv')
# Getsqlite("https://dl.fedoraproject.org/pub/archive/fedora/linux/releases/36/Server/source/tree/")
# plus("http://10.44.34.84/ns8.2/x86/BaseOS/","http://10.44.34.84/ns8.2/x86/AppStream/")
# plus("https://10.44.16.185/NS/V10/8U8/os/adv/lic/AppStream/x86_64/os/","https://10.44.16.185/NS/V10/8U8/os/adv/lic/BaseOS/x86_64/os/")

View File

@@ -0,0 +1,18 @@
## 脚本简介
本脚本用于测试updates仓库中是否有黑名单列表中的软件包。
如果有,列出导出的黑名单软件包
如果没有,则表示测试通过
## 使用方法
执行脚本python3 check_black_rpms_in_repos.py V10SP1.1 V10SP2
参数V10SP1.1 V10SP2为测试的产品列表可以有多个如果没有参数默认测试所有产品
参数的值有:'V10SP1.1', 'V10SP2', 'V10SP3', 'V10SP3-2403', 'V10-HPC', 'V10-ZJ', 'HOST', 'HOST-2309', 'HOST-2406', 'V10SP3-2309A', 'V10SP3-2309B'
## 输出:
测试结果为输出字符串
测试通过:说明无黑名单软件包导出到仓库
测试有报错:说明有黑名单软件包导出到仓库
# 注意事项

View File

@@ -0,0 +1,119 @@
import requests
from bs4 import BeautifulSoup
import re
import json
import sys
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_retry_session():
session = requests.Session()
retry = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
def fetch_rpm_packages(url):
session = create_retry_session()
try:
# 发送 HTTP 请求获取仓库页面内容
response = requests.get(url, timeout=(10, 30)) # 添加超时设置
response.raise_for_status()
# 使用 BeautifulSoup 解析 HTML 内容
soup = BeautifulSoup(response.text, 'html.parser')
# 使用正则表达式匹配 RPM 包文件名
rpm_pattern = re.compile(r'.*\.rpm$')
rpm_links = soup.find_all('a', href=rpm_pattern)
# 提取 RPM 包名称
packages = []
for link in rpm_links:
rpm = link.get('href').replace('%2B', '+') # 替换转移字符+
if rpm.strip():
rpm_parts = rpm.rsplit("-", 2) # 更清晰的变量名
if len(rpm_parts) >= 3: # 确保至少分割出包名、版本和架构
name = rpm_parts[0]
packages.append(name)
else:
print(f"无法解析的软件包: {rpm}")
# 按包名排序并返回
packages.sort()
print(f"从仓库获取到软件包列表,共 {len(packages)} 个 RPM 包")
return packages
except requests.exceptions.RequestException as e:
print(f"获取RPM包列表时出错: {e}")
return [] # 失败时返回空列表
except Exception as e:
print(f"获取RPM包列表时发生未知错误: {e}")
return []
def fetch_and_read_remote_file(url):
try:
# 发送 HTTP 请求获取文件内容,设置超时
response = requests.get(url, timeout=10)
response.raise_for_status()
# 获取文件内容并按行分割
lines = response.text.splitlines()
lines.sort() # 排序
print(f"成功获取黑名单列表,共 {len(lines)} 个 RPM 包")
return lines
except requests.exceptions.HTTPError as http_err:
print(f"黑名单获取HTTP 错误发生: {http_err}")
return []
except requests.exceptions.RequestException as req_err:
print(f"黑名单获取:请求错误发生: {req_err}")
return []
except Exception as err:
print(f"黑名单获取:其他错误发生: {err}")
return []
if __name__ == "__main__":
with open('repository_config.json', 'r', encoding='utf-8') as file:
data = json.load(file)
if len(sys.argv) > 1:
print("测试产品:", sys.argv[1:]) # 跳过脚本名
product_list = sys.argv[1:]
print(product_list)
else:
print("未提供具体测试产品,默认测试所有产品")
product_list = list(data.keys())
print(f"可选其中的值作为脚本参数:{product_list}")
# 解析json文件
for product in product_list:
try:
print(f"\n--------产品【{product}】--------")
product_data = data.get(product, {})
if not product_data:
print(f"警告:未找到产品 {product} 的配置")
continue
for arch, urls in product_data.items():
print(f"\n{arch}仓库:")
if len(urls) < 2:
print(f"警告:{product}/{arch} 配置不完整,跳过")
continue
rpm_packages = fetch_rpm_packages(urls[0])
rpm_black_packages = fetch_and_read_remote_file(urls[1])
set_common = set(rpm_packages) & set(rpm_black_packages)
if set_common:
print(f"测试有报错:发现 {len(set_common)} 个黑名单软件包:{set_common}")
else:
print("测试通过updates仓库没有发现黑名单软件包")
except Exception as err:
print(f"{product} 仓库处理失败:{err}")

View File

@@ -0,0 +1,124 @@
{
"V10SP1.1": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP1.1/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP1.1/os/adv/lic/updates/aarch64/black"
],
"loongarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/loongarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP1.1/os/adv/lic/updates/loongarch64/black"
],
"mips64el": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP1.1/os/adv/lic/updates/mips64el/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP1.1/os/adv/lic/updates/mips64el/black"
]
},
"V10SP2": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP2/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP2/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP2/os/adv/lic/updates/aarch64/black"
]
},
"V10SP3": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP3/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP3/os/adv/lic/updates/aarch64/black"
],
"loongarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP3/os/adv/lic/updates/loongarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP3/os/adv/lic/updates/loongarch64/black"
]
},
"V10SP3-2403": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP3-2403/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP3-2403/os/adv/lic/updates/aarch64/black"
],
"loongarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10SP3-2403/os/adv/lic/updates/loongarch64/black"
]
},
"V10-HPC": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/HPC/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/HPC/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/HPC/os/adv/lic/updates/aarch64/black"
]
},
"V10-ZJ": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10-ZJ/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10-ZJ/os/adv/lic/updates/aarch64/black"
],
"mips64el": [
"https://update.cs2c.com.cn/private_test/repo/V10/V10-ZJ/os/adv/lic/updates/mips64el/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/V10-ZJ/os/adv/lic/updates/mips64el/black"
]
},
"HOST": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/HOST/SP3/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/HOST/SP3/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/HOST/SP3/adv/lic/updates/aarch64/black"
]
},
"HOST-2309": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/HOST/2309/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/HOST/2309/os/adv/lic/updates/aarch64/black"
]
},
"HOST-2406": {
"x86_64": [
"https://update.cs2c.com.cn/private_test/repo/HOST/2406/os/adv/lic/updates/x86_64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/HOST/2406/os/adv/lic/updates/x86_64/black"
],
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/HOST/2309/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/HOST/2309/os/adv/lic/updates/aarch64/black"
]
},
"V10SP3-2309A": {
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/2309A/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/2309A/os/adv/lic/updates/aarch64/black"
]
},
"V10SP3-2309B": {
"aarch64": [
"https://update.cs2c.com.cn/private_test/repo/V10/2309B/os/adv/lic/updates/aarch64/Packages/",
"https://update.cs2c.com.cn/private_test/repo_black/V10/2309B/os/adv/lic/updates/aarch64/black"
]
}
}