依赖和接口扫描是门禁中重要的一环,在CI演进过程中,自然而然的出现了这个需求,在寻求帮助和查看本地bingo帮助后,发现本地工具有依赖和接口检查的功能,但实际应用在CI中需要进行一些修改,以及增量和全量的需求修改。
1.组件扫描
首先先讲一下组件的依赖和接口扫描,以下是组件扫描的大致流程
1.1 schema文件格式校验
这一步主要是扫描后续会用到的一些组件的配置文件,保证格式正确,后续可以正确收集到所使用的所有接口和依赖,需要扫描的文件有:
mds/service.json、mds/types.json、mds/ipmi.json、mds/model.json、mds/debug/service.json、mds/debug/model.json
当需要扫描的文件为json格式时(可以看到我们需要扫描的都是json格式的文件),会先检查文件中是否有$schema字段即规则文件配置(当前都没有),没有的话则判断是否为mds目录下文件(当前文件都为mds目录下文件),然后使用/usr/share/bmcgo/schema目录下的mds.{check_file_name}.schema.v1.json文件进行扫描,如果CI环境不方便进行路径配置的话(实际实践中遇到过类似的问题),可以将该目录下的文件复制到可以使用的路径使用。最后使用jsonschema.validate(检查文件,规则文件)即可。
示例代码(当前仅使用规则文件进行扫描,原因已在上面的规则逻辑中说明):
import jsonschema
from jsonschema import exceptions as jc
@staticmethod
def schema_valid(check_file: str, file_type=YML_TYPE) -> bool:
""" 有效性校验
Args:
check_file (str): 要校验的文件名称
"""
schema_file = BmcgoCommand.get_decleared_schema_file(check_file, file_type)
if not schema_file:
log.warning(f"文件 {check_file} 没有配置 schema 检查")
return True
schema_file_path = os.path.join(os.path.dirname(check_file), schema_file)
log.debug("开始校验文件: %s", check_file)
with open(schema_file, "rb") as fp:
schema = json.load(fp)
with open(check_file, "r") as fp:
if file_type == JSON_TYPE:
try:
check_data = json.load(fp)
except Exception as e:
print(f"解析JSO文件失败: {check_file}, 错误: {e}")
return False
else:
check_data = yaml.safe_load(fp)
try:
jsonschema.validate(check_data, schema)
print("校验成功: %s", check_file)
return True
except (jc.ValidationError, jc.SchemaError) as e:
if os.path.basename(check_file) != 'service.json':
print(f" >>>>>> {check_file} 校验失败 <<<<<<\n{e}")
return True
print(f" >>>>>> {check_file} 校验失败 <<<<<<\n{e}")
return False
tips:如果不为json格式的扫描可查看本地的bingo代码中的misc.py中的get_decleared_schema_file方法
1.2 收集组件中所有定义的接口和依赖
这一步分为3小步
(1)收集组件下mds/model.json中"interfaces"中的所有接口名称
(2)收集组件下mds/service.json中"required"下所有"interface"的内容
(3)收集组件下src目录下的所有.lua文件中使用的接口
代码示例
from collections import defaultdict
from typing import Dict, Set, Any
import json
import os
import re
import shutil
import subprocess
import argparse
model_intfs = analyzer.get_interfaces_from_model_json("HEAD")
service_intfs = analyzer.get_required_interfaces_from_service_json("HEAD")
lua_intfs = analyzer.get_interfaces_from_lua_files("HEAD")
def get_interfaces_from_model_json(self, commit: str) -> Set[str]:
cmd = ["git", "show", f"{commit}:mds/model.json"]
json_content = self.run_git_command(cmd)
if not json_content:
return set()
try:
data = json.loads(json_content)
interfaces = set()
def find_interfaces(obj):
if isinstance(obj, dict):
if "interfaces" in obj and isinstance(obj["interfaces"], dict):
interfaces.update(obj["interfaces"].keys())
for value in obj.values():
find_interfaces(value)
elif isinstance(obj, list):
for item in obj:
find_interfaces(item)
find_interfaces(data)
return interfaces
except Exception as e:
print(f"解析model.json错误: {e}")
return set()
def get_required_interfaces_from_service_json(self, commit: str) -> Set[str]:
cmd = ["git", "show", f"{commit}:mds/service.json"]
json_content = self.run_git_command(cmd)
if not json_content:
return set()
try:
data = json.loads(json_content)
required_interfaces = set()
for obj in data.get("required", []):
if isinstance(obj, dict) and "interface" in obj:
required_interfaces.add(obj["interface"])
return required_interfaces
except Exception as e:
print(f"解析service.json错误: {e}")
return set()
def get_interfaces_from_lua_files(self, commit: str) -> Set[str]:
interfaces = set()
cmd = ["git", "ls-tree", "-r", commit, "--name-only"]
files = self.run_git_command(cmd)
if not files:
return interfaces
for file_path in files.split('\n'):
if file_path.startswith("src/") and file_path.endswith(".lua"):
show_cmd = ["git", "show", f"{commit}:{file_path}"]
file_content = self.run_git_command(show_cmd)
if file_content:
pattern = r"[\"\']bmc\.(?:kepler|dev)(?:\.[A-Z].*?)?[\"\']"
intf_match = re.findall(pattern, file_content)
for intf in intf_match:
interfaces.add(intf[1:-1])
return interfaces
1.3 收集所有可用的依赖和接口
这一步也可以分为3小步
(1)拉取社区mdb_interface组件并收集json/intf/mdb/bmc文件夹下所有json文件写明的接口
(2)读取组件依赖的mdb_interface中所记录的接口。这一步现在需要区别是conan1还是conan2。
首先检查组件的mds/service.json的dependencies字段中查找mdb_interface组件的依赖,如果没有,conan1会拉取并使用mdb_interface/[>=0.0.1]@openUBMC.release/rc,若为conan2则使用mdb_interface/[>=0.0.1]@openubmc/stable;若有记录版本则判断是否指定具体版本,有则使用具体版本,没有则使用大致范围加上@openUBMC.release/rc(conan1)或者@openubmc/stable(conan2),最后使用conan1或conan2的命令拉取相应版本组件,并记录mdb_interface/opt/bmc/apps/mdb_interface/intf/mdb/bmc下所有json文件所记录的接口
(3)读取gen文件中的记录的接口:获取gen/组件名/json_types文件夹下所有lua文件中记录的接口;获取gen/组件名/json_types/client.lua下所有记录的接口
代码示例(实践时未有conan2场景):
补充中。。。
