Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
616838a9a8
|
|
@ -12,6 +12,8 @@ main_repository:
|
||||||
monitor:
|
monitor:
|
||||||
poll_interval: 10 # 轮询间隔(秒)
|
poll_interval: 10 # 轮询间隔(秒)
|
||||||
enabled_repos: [] # 空数组表示监听所有仓库,或指定具体仓库名称列表
|
enabled_repos: [] # 空数组表示监听所有仓库,或指定具体仓库名称列表
|
||||||
|
watch_tags: true # 是否监听 tag 变化(打 tag 时触发部署)
|
||||||
|
tag_pattern: "v*" # 监听的 tag 模式,支持通配符(如 v*、release-*)
|
||||||
|
|
||||||
# 部署配置
|
# 部署配置
|
||||||
deploy:
|
deploy:
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,13 @@ class GitMonitor:
|
||||||
self.config_path = config_path
|
self.config_path = config_path
|
||||||
self.config = None
|
self.config = None
|
||||||
self.last_commits = {}
|
self.last_commits = {}
|
||||||
|
self.last_tags = {} # 记录每个仓库的最新 tag
|
||||||
self.global_branch = 'main'
|
self.global_branch = 'main'
|
||||||
self.project_root = None
|
self.project_root = None
|
||||||
self.runtime_path = None
|
self.runtime_path = None
|
||||||
self.dingtalk_notifier = None
|
self.dingtalk_notifier = None
|
||||||
|
self.watch_tags = False
|
||||||
|
self.tag_pattern = "v*"
|
||||||
|
|
||||||
# 初始化
|
# 初始化
|
||||||
self._print_startup_banner()
|
self._print_startup_banner()
|
||||||
|
|
@ -62,6 +65,11 @@ class GitMonitor:
|
||||||
|
|
||||||
self.global_branch = self.config.get('global_branch', 'main')
|
self.global_branch = self.config.get('global_branch', 'main')
|
||||||
|
|
||||||
|
# 加载 tag 监听配置
|
||||||
|
monitor_config = self.config.get('monitor', {})
|
||||||
|
self.watch_tags = monitor_config.get('watch_tags', False)
|
||||||
|
self.tag_pattern = monitor_config.get('tag_pattern', 'v*')
|
||||||
|
|
||||||
# 初始化日志配置
|
# 初始化日志配置
|
||||||
log_config = self.config.get('logging', {})
|
log_config = self.config.get('logging', {})
|
||||||
log_file = log_config.get('file', '.devops/logs/devops.log')
|
log_file = log_config.get('file', '.devops/logs/devops.log')
|
||||||
|
|
@ -69,6 +77,7 @@ class GitMonitor:
|
||||||
Logger.init(log_file=log_file, max_size=max_size)
|
Logger.init(log_file=log_file, max_size=max_size)
|
||||||
|
|
||||||
Logger.info(f"✓ 配置加载成功 - 全局分支: {self.global_branch}")
|
Logger.info(f"✓ 配置加载成功 - 全局分支: {self.global_branch}")
|
||||||
|
Logger.info(f"✓ Tag 监听: {'已启用' if self.watch_tags else '未启用'} (模式: {self.tag_pattern})")
|
||||||
Logger.info(f"✓ 日志配置 - 文件: {log_file}, 最大大小: {max_size} 字节")
|
Logger.info(f"✓ 日志配置 - 文件: {log_file}, 最大大小: {max_size} 字节")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.error(f"配置加载失败: {e}")
|
Logger.error(f"配置加载失败: {e}")
|
||||||
|
|
@ -154,6 +163,32 @@ class GitMonitor:
|
||||||
Logger.error(f"获取提交消息失败: {e}")
|
Logger.error(f"获取提交消息失败: {e}")
|
||||||
return f"提交 {commit_hash[:8]}"
|
return f"提交 {commit_hash[:8]}"
|
||||||
|
|
||||||
|
def get_remote_tags(self, repo_url):
|
||||||
|
"""获取远程仓库的所有 tags"""
|
||||||
|
try:
|
||||||
|
cmd = f"git ls-remote --tags {repo_url}"
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd, shell=True, capture_output=True, text=True, timeout=30
|
||||||
|
)
|
||||||
|
if result.returncode == 0 and result.stdout:
|
||||||
|
tags = {}
|
||||||
|
for line in result.stdout.strip().split('\n'):
|
||||||
|
if line:
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 2:
|
||||||
|
commit_hash = parts[0]
|
||||||
|
ref = parts[1]
|
||||||
|
# 提取 tag 名称,去掉 refs/tags/ 前缀和 ^{} 后缀
|
||||||
|
if ref.startswith('refs/tags/'):
|
||||||
|
tag_name = ref.replace('refs/tags/', '')
|
||||||
|
if not tag_name.endswith('^{}'):
|
||||||
|
tags[tag_name] = commit_hash
|
||||||
|
return tags
|
||||||
|
return {}
|
||||||
|
except Exception as e:
|
||||||
|
Logger.error(f"获取远程 tags 失败 {repo_url}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
def check_repository(self, repo_config):
|
def check_repository(self, repo_config):
|
||||||
"""检查单个仓库是否有新提交"""
|
"""检查单个仓库是否有新提交"""
|
||||||
repo_name = repo_config['name']
|
repo_name = repo_config['name']
|
||||||
|
|
@ -176,6 +211,51 @@ class GitMonitor:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def check_repository_tags(self, repo_config):
|
||||||
|
"""检查单个仓库是否有新 tag"""
|
||||||
|
repo_name = repo_config['name']
|
||||||
|
repo_url = repo_config['url']
|
||||||
|
|
||||||
|
# 获取远程所有 tags
|
||||||
|
current_tags = self.get_remote_tags(repo_url)
|
||||||
|
if current_tags is None or (not current_tags and repo_name not in self.last_tags):
|
||||||
|
# 首次获取失败或获取失败时发送通知
|
||||||
|
error_msg = f"获取 {repo_name} 远程 tags 失败"
|
||||||
|
Logger.error(error_msg)
|
||||||
|
if self.dingtalk_notifier and repo_name in self.last_tags:
|
||||||
|
# 只有在之前成功获取过 tags 的情况下才发送通知,避免首次初始化时发送
|
||||||
|
self.dingtalk_notifier.send_build_failure(
|
||||||
|
repo_name=repo_name,
|
||||||
|
branch=self.global_branch,
|
||||||
|
commit_hash='unknown',
|
||||||
|
error_msg=error_msg
|
||||||
|
)
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# 获取上次记录的 tags
|
||||||
|
last_tags = self.last_tags.get(repo_name, {})
|
||||||
|
|
||||||
|
# 找出新增的 tags
|
||||||
|
new_tags = []
|
||||||
|
for tag_name, commit_hash in current_tags.items():
|
||||||
|
# 检查 tag 是否匹配模式
|
||||||
|
import fnmatch
|
||||||
|
if fnmatch.fnmatch(tag_name, self.tag_pattern):
|
||||||
|
if tag_name not in last_tags:
|
||||||
|
new_tags.append((tag_name, commit_hash))
|
||||||
|
|
||||||
|
# 更新记录
|
||||||
|
self.last_tags[repo_name] = current_tags
|
||||||
|
|
||||||
|
if new_tags:
|
||||||
|
# 返回最新的 tag
|
||||||
|
new_tags.sort(reverse=True) # 按名称排序,最新的在前
|
||||||
|
latest_tag = new_tags[0]
|
||||||
|
Logger.info(f"检测到 {repo_name} 新 tag: {latest_tag[0]} ({latest_tag[1][:8]})")
|
||||||
|
return True, latest_tag
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
def update_main_repo(self):
|
def update_main_repo(self):
|
||||||
"""更新主仓库和所有子模块"""
|
"""更新主仓库和所有子模块"""
|
||||||
repo_path = self.runtime_path / 'a-cloud-all'
|
repo_path = self.runtime_path / 'a-cloud-all'
|
||||||
|
|
@ -311,8 +391,13 @@ class GitMonitor:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def deploy(self, repo_config):
|
def deploy(self, repo_config, tag_name=None):
|
||||||
"""执行部署流程"""
|
"""执行部署流程
|
||||||
|
|
||||||
|
参数:
|
||||||
|
repo_config: 仓库配置
|
||||||
|
tag_name: 可选的 tag 名称,如果提供则表示这是由 tag 触发的部署
|
||||||
|
"""
|
||||||
repo_path = self.runtime_path / 'a-cloud-all'
|
repo_path = self.runtime_path / 'a-cloud-all'
|
||||||
repo_name = repo_config['name']
|
repo_name = repo_config['name']
|
||||||
commit_hash = self.last_commits.get(repo_name, 'unknown')
|
commit_hash = self.last_commits.get(repo_name, 'unknown')
|
||||||
|
|
@ -320,11 +405,24 @@ class GitMonitor:
|
||||||
|
|
||||||
Logger.separator()
|
Logger.separator()
|
||||||
Logger.info(f"开始部署: {repo_name}")
|
Logger.info(f"开始部署: {repo_name}")
|
||||||
|
if tag_name:
|
||||||
|
Logger.info(f"触发方式: Tag ({tag_name})")
|
||||||
|
else:
|
||||||
|
Logger.info(f"触发方式: 分支提交")
|
||||||
Logger.separator()
|
Logger.separator()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 1. 更新主仓库和子模块
|
# 1. 更新主仓库和子模块
|
||||||
if not self.update_main_repo():
|
if not self.update_main_repo():
|
||||||
|
# 发送 Git 更新失败通知
|
||||||
|
if self.dingtalk_notifier:
|
||||||
|
duration = time.time() - start_time
|
||||||
|
self.dingtalk_notifier.send_build_failure(
|
||||||
|
repo_name=repo_name,
|
||||||
|
branch=self.global_branch,
|
||||||
|
commit_hash=commit_hash,
|
||||||
|
error_msg="Git 仓库更新失败(主仓库或子模块)"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 获取子仓库的 commit message
|
# 获取子仓库的 commit message
|
||||||
|
|
@ -348,16 +446,30 @@ class GitMonitor:
|
||||||
|
|
||||||
# 发送构建开始通知(包含 commit message 和服务器 IP)
|
# 发送构建开始通知(包含 commit message 和服务器 IP)
|
||||||
if self.dingtalk_notifier:
|
if self.dingtalk_notifier:
|
||||||
|
# 如果是 tag 触发,在 commit_message 中添加 tag 信息
|
||||||
|
display_message = commit_message
|
||||||
|
if tag_name:
|
||||||
|
display_message = f"Tag: {tag_name}" + (f" - {commit_message}" if commit_message else "")
|
||||||
|
|
||||||
self.dingtalk_notifier.send_build_start(
|
self.dingtalk_notifier.send_build_start(
|
||||||
repo_name=repo_name,
|
repo_name=repo_name,
|
||||||
branch=self.global_branch,
|
branch=self.global_branch if not tag_name else f"tag/{tag_name}",
|
||||||
commit_hash=commit_hash,
|
commit_hash=commit_hash,
|
||||||
commit_message=commit_message,
|
commit_message=display_message,
|
||||||
server_ip=server_ip
|
server_ip=server_ip
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. 初始化基础设施
|
# 2. 初始化基础设施
|
||||||
if not self.init_infrastructure():
|
if not self.init_infrastructure():
|
||||||
|
# 发送基础设施初始化失败通知
|
||||||
|
if self.dingtalk_notifier:
|
||||||
|
duration = time.time() - start_time
|
||||||
|
self.dingtalk_notifier.send_build_failure(
|
||||||
|
repo_name=repo_name,
|
||||||
|
branch=self.global_branch,
|
||||||
|
commit_hash=commit_hash,
|
||||||
|
error_msg="基础设施初始化失败(MySQL/Redis/Nacos等)"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 3. 根据项目类型执行打包
|
# 3. 根据项目类型执行打包
|
||||||
|
|
@ -368,7 +480,17 @@ class GitMonitor:
|
||||||
source_path = repo_config['path'] + '/' + repo_config['artifact_path']
|
source_path = repo_config['path'] + '/' + repo_config['artifact_path']
|
||||||
target_dir = repo_path / repo_config['docker_path']
|
target_dir = repo_path / repo_config['docker_path']
|
||||||
|
|
||||||
if not maven.run_maven(work_dir, commands, source_path, target_dir):
|
success, error_msg = maven.run_maven(work_dir, commands, source_path, target_dir)
|
||||||
|
if not success:
|
||||||
|
# 发送 Maven 构建失败通知
|
||||||
|
if self.dingtalk_notifier:
|
||||||
|
duration = time.time() - start_time
|
||||||
|
self.dingtalk_notifier.send_build_failure(
|
||||||
|
repo_name=repo_name,
|
||||||
|
branch=self.global_branch,
|
||||||
|
commit_hash=commit_hash,
|
||||||
|
error_msg=f"Maven 打包失败: {error_msg}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif repo_config['type'] == 'nodejs':
|
elif repo_config['type'] == 'nodejs':
|
||||||
|
|
@ -378,7 +500,17 @@ class GitMonitor:
|
||||||
source_dir = repo_config['artifact_path']
|
source_dir = repo_config['artifact_path']
|
||||||
target_dir = repo_path / repo_config['docker_path']
|
target_dir = repo_path / repo_config['docker_path']
|
||||||
|
|
||||||
if not npm.run_npm(work_dir, commands, source_dir, target_dir):
|
success, error_msg = npm.run_npm(work_dir, commands, source_dir, target_dir)
|
||||||
|
if not success:
|
||||||
|
# 发送 NPM/PNPM 构建失败通知
|
||||||
|
if self.dingtalk_notifier:
|
||||||
|
duration = time.time() - start_time
|
||||||
|
self.dingtalk_notifier.send_build_failure(
|
||||||
|
repo_name=repo_name,
|
||||||
|
branch=self.global_branch,
|
||||||
|
commit_hash=commit_hash,
|
||||||
|
error_msg=f"NPM/PNPM 打包失败: {error_msg}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 4. Docker 部署
|
# 4. Docker 部署
|
||||||
|
|
@ -436,14 +568,40 @@ class GitMonitor:
|
||||||
|
|
||||||
for repo_config in repos:
|
for repo_config in repos:
|
||||||
try:
|
try:
|
||||||
|
# 检查分支提交
|
||||||
if self.check_repository(repo_config):
|
if self.check_repository(repo_config):
|
||||||
Logger.info(f"触发部署: {repo_config['name']}")
|
Logger.info(f"触发部署: {repo_config['name']} (分支提交)")
|
||||||
if self.deploy(repo_config):
|
if self.deploy(repo_config):
|
||||||
Logger.info(f"✓ 部署成功: {repo_config['name']}")
|
Logger.info(f"✓ 部署成功: {repo_config['name']}")
|
||||||
else:
|
else:
|
||||||
Logger.error(f"✗ 部署失败: {repo_config['name']}")
|
Logger.error(f"✗ 部署失败: {repo_config['name']}")
|
||||||
|
continue # 已经部署,跳过 tag 检查
|
||||||
|
|
||||||
|
# 检查 tag(如果启用)
|
||||||
|
if self.watch_tags:
|
||||||
|
has_new_tag, tag_info = self.check_repository_tags(repo_config)
|
||||||
|
if has_new_tag and tag_info:
|
||||||
|
tag_name, commit_hash = tag_info
|
||||||
|
Logger.info(f"触发部署: {repo_config['name']} (新 tag: {tag_name})")
|
||||||
|
# 更新 last_commits 以便 deploy 方法使用
|
||||||
|
self.last_commits[repo_config['name']] = commit_hash
|
||||||
|
if self.deploy(repo_config, tag_name=tag_name):
|
||||||
|
Logger.info(f"✓ 部署成功: {repo_config['name']}")
|
||||||
|
else:
|
||||||
|
Logger.error(f"✗ 部署失败: {repo_config['name']}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.error(f"处理仓库异常 {repo_config['name']}: {e}")
|
Logger.error(f"处理仓库异常 {repo_config['name']}: {e}")
|
||||||
|
# 发送异常通知
|
||||||
|
if self.dingtalk_notifier:
|
||||||
|
commit_hash = self.last_commits.get(repo_config['name'], 'unknown')
|
||||||
|
self.dingtalk_notifier.send_build_failure(
|
||||||
|
repo_name=repo_config['name'],
|
||||||
|
branch=self.global_branch,
|
||||||
|
commit_hash=commit_hash,
|
||||||
|
error_msg=f"处理仓库时发生异常: {str(e)}",
|
||||||
|
at_all=True
|
||||||
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""持续监听运行"""
|
"""持续监听运行"""
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
|
||||||
target_dir: 复制的目标目录
|
target_dir: 复制的目标目录
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
bool: 成功返回 True,失败返回 False
|
tuple: (success: bool, error_message: str) 成功返回 (True, ""),失败返回 (False, "错误信息")
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 转换为绝对路径
|
# 转换为绝对路径
|
||||||
|
|
@ -36,13 +36,15 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
|
||||||
|
|
||||||
# 检查目录是否存在
|
# 检查目录是否存在
|
||||||
if not work_dir.exists():
|
if not work_dir.exists():
|
||||||
Logger.error(f"目录不存在: {work_dir}")
|
error_msg = f"目录不存在: {work_dir}"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
# 执行 Maven 命令
|
# 执行 Maven 命令
|
||||||
if not Logger.run_command(maven_commands, work_dir):
|
if not Logger.run_command(maven_commands, work_dir):
|
||||||
Logger.error("Maven 打包失败")
|
error_msg = "Maven 编译失败,请查看日志获取详细错误信息"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
Logger.info("Maven 打包成功")
|
Logger.info("Maven 打包成功")
|
||||||
|
|
||||||
|
|
@ -61,8 +63,9 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
|
||||||
# 复制文件
|
# 复制文件
|
||||||
files = glob.glob(str(source_full_path))
|
files = glob.glob(str(source_full_path))
|
||||||
if not files:
|
if not files:
|
||||||
Logger.error(f"未找到构建产物: {source_full_path}")
|
error_msg = f"未找到构建产物: {source_full_path}"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
file_path = Path(file)
|
file_path = Path(file)
|
||||||
|
|
@ -74,8 +77,9 @@ def run_maven(work_dir, maven_commands, source_path, target_dir):
|
||||||
|
|
||||||
Logger.info("构建产物复制成功")
|
Logger.info("构建产物复制成功")
|
||||||
Logger.info("Maven 打包和复制完成")
|
Logger.info("Maven 打包和复制完成")
|
||||||
return True
|
return True, ""
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.error(f"Maven 打包异常: {e}")
|
error_msg = f"Maven 打包异常: {str(e)}"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
|
||||||
target_dir: 复制的目标目录
|
target_dir: 复制的目标目录
|
||||||
|
|
||||||
返回:
|
返回:
|
||||||
bool: 成功返回 True,失败返回 False
|
tuple: (success: bool, error_message: str) 成功返回 (True, ""),失败返回 (False, "错误信息")
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 转换为绝对路径
|
# 转换为绝对路径
|
||||||
|
|
@ -36,13 +36,15 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
|
||||||
|
|
||||||
# 检查目录是否存在
|
# 检查目录是否存在
|
||||||
if not work_dir.exists():
|
if not work_dir.exists():
|
||||||
Logger.error(f"目录不存在: {work_dir}")
|
error_msg = f"目录不存在: {work_dir}"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
# 执行 NPM 命令
|
# 执行 NPM 命令
|
||||||
if not Logger.run_command(npm_commands, work_dir):
|
if not Logger.run_command(npm_commands, work_dir):
|
||||||
Logger.error("NPM 打包失败")
|
error_msg = "NPM/PNPM 编译失败,请查看日志获取详细错误信息"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
Logger.info("NPM 打包成功")
|
Logger.info("NPM 打包成功")
|
||||||
|
|
||||||
|
|
@ -57,8 +59,9 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
|
||||||
|
|
||||||
# 检查源目录是否存在
|
# 检查源目录是否存在
|
||||||
if not source_full_path.exists():
|
if not source_full_path.exists():
|
||||||
Logger.error(f"源目录不存在: {source_full_path}")
|
error_msg = f"源目录不存在: {source_full_path}"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
# 清空目标目录(保留 .gitkeep)
|
# 清空目标目录(保留 .gitkeep)
|
||||||
target_path = Path(target_dir)
|
target_path = Path(target_dir)
|
||||||
|
|
@ -84,8 +87,9 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir):
|
||||||
|
|
||||||
Logger.info("构建产物复制成功")
|
Logger.info("构建产物复制成功")
|
||||||
Logger.info("NPM 打包和复制完成")
|
Logger.info("NPM 打包和复制完成")
|
||||||
return True
|
return True, ""
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.error(f"NPM 打包异常: {e}")
|
error_msg = f"NPM 打包异常: {str(e)}"
|
||||||
return False
|
Logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ public class AircraftDetailVO extends AircraftVO {
|
||||||
/** RTK信号 */
|
/** RTK信号 */
|
||||||
@Schema(description = "RTK信号")
|
@Schema(description = "RTK信号")
|
||||||
@Excel(name = "RTK信号")
|
@Excel(name = "RTK信号")
|
||||||
private Double rtkSignal;
|
private Integer rtkSignal;
|
||||||
|
|
||||||
/** 限高 */
|
/** 限高 */
|
||||||
@Schema(description = "限高")
|
@Schema(description = "限高")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue