diff --git a/.devops/config.yaml b/.devops/config.yaml index 2e686bd..6ed9c96 100644 --- a/.devops/config.yaml +++ b/.devops/config.yaml @@ -12,6 +12,8 @@ main_repository: monitor: poll_interval: 10 # 轮询间隔(秒) enabled_repos: [] # 空数组表示监听所有仓库,或指定具体仓库名称列表 + watch_tags: true # 是否监听 tag 变化(打 tag 时触发部署) + tag_pattern: "v*" # 监听的 tag 模式,支持通配符(如 v*、release-*) # 部署配置 deploy: diff --git a/.devops/monitor.py b/.devops/monitor.py index e1207a6..3ba59a6 100644 --- a/.devops/monitor.py +++ b/.devops/monitor.py @@ -32,10 +32,13 @@ class GitMonitor: self.config_path = config_path self.config = None self.last_commits = {} + self.last_tags = {} # 记录每个仓库的最新 tag self.global_branch = 'main' self.project_root = None self.runtime_path = None self.dingtalk_notifier = None + self.watch_tags = False + self.tag_pattern = "v*" # 初始化 self._print_startup_banner() @@ -62,6 +65,11 @@ class GitMonitor: 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_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.info(f"✓ 配置加载成功 - 全局分支: {self.global_branch}") + Logger.info(f"✓ Tag 监听: {'已启用' if self.watch_tags else '未启用'} (模式: {self.tag_pattern})") Logger.info(f"✓ 日志配置 - 文件: {log_file}, 最大大小: {max_size} 字节") except Exception as e: Logger.error(f"配置加载失败: {e}") @@ -154,6 +163,32 @@ class GitMonitor: Logger.error(f"获取提交消息失败: {e}") 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): """检查单个仓库是否有新提交""" repo_name = repo_config['name'] @@ -176,6 +211,51 @@ class GitMonitor: 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): """更新主仓库和所有子模块""" repo_path = self.runtime_path / 'a-cloud-all' @@ -311,8 +391,13 @@ class GitMonitor: 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_name = repo_config['name'] commit_hash = self.last_commits.get(repo_name, 'unknown') @@ -320,6 +405,10 @@ class GitMonitor: Logger.separator() Logger.info(f"开始部署: {repo_name}") + if tag_name: + Logger.info(f"触发方式: Tag ({tag_name})") + else: + Logger.info(f"触发方式: 分支提交") Logger.separator() try: @@ -357,11 +446,16 @@ class GitMonitor: # 发送构建开始通知(包含 commit message 和服务器 IP) 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( 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_message=commit_message, + commit_message=display_message, server_ip=server_ip ) @@ -386,7 +480,8 @@ class GitMonitor: source_path = repo_config['path'] + '/' + repo_config['artifact_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 @@ -394,7 +489,7 @@ class GitMonitor: repo_name=repo_name, branch=self.global_branch, commit_hash=commit_hash, - error_msg="Maven 打包失败" + error_msg=f"Maven 打包失败: {error_msg}" ) return False @@ -405,7 +500,8 @@ class GitMonitor: source_dir = repo_config['artifact_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 @@ -413,7 +509,7 @@ class GitMonitor: repo_name=repo_name, branch=self.global_branch, commit_hash=commit_hash, - error_msg="NPM/PNPM 打包失败" + error_msg=f"NPM/PNPM 打包失败: {error_msg}" ) return False @@ -472,12 +568,28 @@ class GitMonitor: for repo_config in repos: try: + # 检查分支提交 if self.check_repository(repo_config): - Logger.info(f"触发部署: {repo_config['name']}") + Logger.info(f"触发部署: {repo_config['name']} (分支提交)") if self.deploy(repo_config): Logger.info(f"✓ 部署成功: {repo_config['name']}") else: 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: Logger.error(f"处理仓库异常 {repo_config['name']}: {e}") # 发送异常通知 diff --git a/.devops/scripts/maven.py b/.devops/scripts/maven.py index a387394..f92fff1 100644 --- a/.devops/scripts/maven.py +++ b/.devops/scripts/maven.py @@ -22,7 +22,7 @@ def run_maven(work_dir, maven_commands, source_path, target_dir): target_dir: 复制的目标目录 返回: - bool: 成功返回 True,失败返回 False + tuple: (success: bool, error_message: str) 成功返回 (True, ""),失败返回 (False, "错误信息") """ try: # 转换为绝对路径 @@ -36,13 +36,15 @@ def run_maven(work_dir, maven_commands, source_path, target_dir): # 检查目录是否存在 if not work_dir.exists(): - Logger.error(f"目录不存在: {work_dir}") - return False + error_msg = f"目录不存在: {work_dir}" + Logger.error(error_msg) + return False, error_msg # 执行 Maven 命令 if not Logger.run_command(maven_commands, work_dir): - Logger.error("Maven 打包失败") - return False + error_msg = "Maven 编译失败,请查看日志获取详细错误信息" + Logger.error(error_msg) + return False, error_msg 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)) if not files: - Logger.error(f"未找到构建产物: {source_full_path}") - return False + error_msg = f"未找到构建产物: {source_full_path}" + Logger.error(error_msg) + return False, error_msg for file in files: file_path = Path(file) @@ -74,8 +77,9 @@ def run_maven(work_dir, maven_commands, source_path, target_dir): Logger.info("构建产物复制成功") Logger.info("Maven 打包和复制完成") - return True + return True, "" except Exception as e: - Logger.error(f"Maven 打包异常: {e}") - return False + error_msg = f"Maven 打包异常: {str(e)}" + Logger.error(error_msg) + return False, error_msg diff --git a/.devops/scripts/npm.py b/.devops/scripts/npm.py index 2028b4c..473305d 100644 --- a/.devops/scripts/npm.py +++ b/.devops/scripts/npm.py @@ -22,7 +22,7 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir): target_dir: 复制的目标目录 返回: - bool: 成功返回 True,失败返回 False + tuple: (success: bool, error_message: str) 成功返回 (True, ""),失败返回 (False, "错误信息") """ try: # 转换为绝对路径 @@ -36,13 +36,15 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir): # 检查目录是否存在 if not work_dir.exists(): - Logger.error(f"目录不存在: {work_dir}") - return False + error_msg = f"目录不存在: {work_dir}" + Logger.error(error_msg) + return False, error_msg # 执行 NPM 命令 if not Logger.run_command(npm_commands, work_dir): - Logger.error("NPM 打包失败") - return False + error_msg = "NPM/PNPM 编译失败,请查看日志获取详细错误信息" + Logger.error(error_msg) + return False, error_msg Logger.info("NPM 打包成功") @@ -57,8 +59,9 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir): # 检查源目录是否存在 if not source_full_path.exists(): - Logger.error(f"源目录不存在: {source_full_path}") - return False + error_msg = f"源目录不存在: {source_full_path}" + Logger.error(error_msg) + return False, error_msg # 清空目标目录(保留 .gitkeep) target_path = Path(target_dir) @@ -84,8 +87,9 @@ def run_npm(work_dir, npm_commands, source_dir, target_dir): Logger.info("构建产物复制成功") Logger.info("NPM 打包和复制完成") - return True + return True, "" except Exception as e: - Logger.error(f"NPM 打包异常: {e}") - return False + error_msg = f"NPM 打包异常: {str(e)}" + Logger.error(error_msg) + return False, error_msg