修改脚本
This commit is contained in:
parent
0db6eecceb
commit
da82d8ee17
|
|
@ -11,8 +11,11 @@ import time
|
||||||
import yaml
|
import yaml
|
||||||
import subprocess
|
import subprocess
|
||||||
import socket
|
import socket
|
||||||
|
import threading
|
||||||
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from flask import Flask, request, jsonify, render_template_string
|
||||||
|
|
||||||
# 添加当前目录到 Python 路径
|
# 添加当前目录到 Python 路径
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
@ -659,6 +662,62 @@ class GitMonitor:
|
||||||
at_all=True
|
at_all=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def deploy_by_name(self, repo_name):
|
||||||
|
"""通过项目名称触发部署
|
||||||
|
|
||||||
|
参数:
|
||||||
|
repo_name: 项目名称
|
||||||
|
|
||||||
|
返回:
|
||||||
|
(success, message): 成功标志和消息
|
||||||
|
"""
|
||||||
|
repos = self.config.get('repositories', [])
|
||||||
|
|
||||||
|
# 查找匹配的仓库配置
|
||||||
|
repo_config = None
|
||||||
|
for repo in repos:
|
||||||
|
if repo['name'] == repo_name:
|
||||||
|
repo_config = repo
|
||||||
|
break
|
||||||
|
|
||||||
|
if not repo_config:
|
||||||
|
return False, f"未找到项目: {repo_name}"
|
||||||
|
|
||||||
|
Logger.info(f"HTTP触发部署: {repo_name}")
|
||||||
|
|
||||||
|
# 获取最新的commit hash
|
||||||
|
repo_url = repo_config['url']
|
||||||
|
current_commit = self.get_remote_commit(repo_url, self.global_branch)
|
||||||
|
|
||||||
|
if current_commit:
|
||||||
|
self.last_commits[repo_name] = current_commit
|
||||||
|
|
||||||
|
# 执行部署
|
||||||
|
success = self.deploy(repo_config)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return True, f"项目 {repo_name} 部署成功"
|
||||||
|
else:
|
||||||
|
return False, f"项目 {repo_name} 部署失败"
|
||||||
|
|
||||||
|
def get_all_projects(self):
|
||||||
|
"""获取所有可部署的项目列表
|
||||||
|
|
||||||
|
返回:
|
||||||
|
项目列表,每个项目包含 name, type, docker_service
|
||||||
|
"""
|
||||||
|
repos = self.config.get('repositories', [])
|
||||||
|
projects = []
|
||||||
|
|
||||||
|
for repo in repos:
|
||||||
|
projects.append({
|
||||||
|
'name': repo['name'],
|
||||||
|
'type': repo['type'],
|
||||||
|
'docker_service': repo['docker_service']
|
||||||
|
})
|
||||||
|
|
||||||
|
return projects
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""持续监听运行"""
|
"""持续监听运行"""
|
||||||
poll_interval = self.config['monitor']['poll_interval']
|
poll_interval = self.config['monitor']['poll_interval']
|
||||||
|
|
@ -675,6 +734,365 @@ class GitMonitor:
|
||||||
Logger.error(f"监听异常: {e}")
|
Logger.error(f"监听异常: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class DeploymentServer:
|
||||||
|
"""HTTP部署服务器"""
|
||||||
|
|
||||||
|
def __init__(self, monitor, port=9999):
|
||||||
|
"""初始化HTTP服务器
|
||||||
|
|
||||||
|
参数:
|
||||||
|
monitor: GitMonitor实例
|
||||||
|
port: HTTP服务器端口,默认9999
|
||||||
|
"""
|
||||||
|
self.monitor = monitor
|
||||||
|
self.port = port
|
||||||
|
self.app = Flask(__name__)
|
||||||
|
self._setup_routes()
|
||||||
|
|
||||||
|
def _setup_routes(self):
|
||||||
|
"""设置路由"""
|
||||||
|
|
||||||
|
@self.app.route('/')
|
||||||
|
def index():
|
||||||
|
"""根路径 - 显示操作说明"""
|
||||||
|
projects = self.monitor.get_all_projects()
|
||||||
|
|
||||||
|
html = '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>DevOps 部署系统</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #1976d2;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.deploy-link {
|
||||||
|
color: #1976d2;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.deploy-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.type-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.type-java {
|
||||||
|
background-color: #ff9800;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.type-nodejs {
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.type-python {
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🚀 RuoYi Cloud DevOps 部署系统</h1>
|
||||||
|
<div class="info">
|
||||||
|
<h3>📖 使用说明</h3>
|
||||||
|
<p><strong>触发部署:</strong>点击下方表格中的"部署链接",或直接访问 <code>http://IP:9999/项目名称</code></p>
|
||||||
|
<p><strong>示例:</strong><code>http://IP:9999/tuoheng-device</code> 触发 tuoheng-device 项目部署</p>
|
||||||
|
<p><strong>查看日志:</strong><a href="/logs" style="color: #1976d2; font-weight: bold;">点击这里查看部署日志</a></p>
|
||||||
|
</div>
|
||||||
|
<h2>📦 可部署项目列表</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>项目名称</th>
|
||||||
|
<th>类型</th>
|
||||||
|
<th>Docker服务</th>
|
||||||
|
<th>部署链接</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
'''
|
||||||
|
|
||||||
|
for project in projects:
|
||||||
|
type_class = f"type-{project['type']}"
|
||||||
|
html += f'''
|
||||||
|
<tr>
|
||||||
|
<td>{project['name']}</td>
|
||||||
|
<td><span class="{type_class} type-badge">{project['type'].upper()}</span></td>
|
||||||
|
<td>{project['docker_service']}</td>
|
||||||
|
<td><a href="/{project['name']}" class="deploy-link">/{project['name']}</a></td>
|
||||||
|
</tr>
|
||||||
|
'''
|
||||||
|
|
||||||
|
html += '''
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
@self.app.route('/logs')
|
||||||
|
def view_logs():
|
||||||
|
"""查看日志页面"""
|
||||||
|
html = '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>DevOps 日志查看</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #2d2d30;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #4ec9b0;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #0e639c;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #1177bb;
|
||||||
|
}
|
||||||
|
.log-container {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border: 1px solid #3e3e42;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
height: calc(100vh - 180px);
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.log-line {
|
||||||
|
margin: 2px 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.log-error {
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
.log-warning {
|
||||||
|
color: #dcdcaa;
|
||||||
|
}
|
||||||
|
.log-info {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
.log-success {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #858585;
|
||||||
|
}
|
||||||
|
.back-link {
|
||||||
|
color: #4ec9b0;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.back-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<div>
|
||||||
|
<h1>📋 DevOps 部署日志</h1>
|
||||||
|
<a href="/" class="back-link">← 返回首页</a>
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<button onclick="refreshLogs()">🔄 刷新</button>
|
||||||
|
<button onclick="clearDisplay()">🗑️ 清空显示</button>
|
||||||
|
<button onclick="toggleAutoRefresh()">⏱️ <span id="autoRefreshText">开启自动刷新</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="log-container" id="logContainer">
|
||||||
|
<div class="loading">正在加载日志...</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let autoRefreshInterval = null;
|
||||||
|
let isAutoRefresh = false;
|
||||||
|
|
||||||
|
function formatLogLine(line) {
|
||||||
|
if (line.includes('ERROR') || line.includes('失败') || line.includes('异常')) {
|
||||||
|
return '<div class="log-line log-error">' + escapeHtml(line) + '</div>';
|
||||||
|
} else if (line.includes('WARNING') || line.includes('警告')) {
|
||||||
|
return '<div class="log-line log-warning">' + escapeHtml(line) + '</div>';
|
||||||
|
} else if (line.includes('INFO') || line.includes('✓')) {
|
||||||
|
return '<div class="log-line log-info">' + escapeHtml(line) + '</div>';
|
||||||
|
} else if (line.includes('成功') || line.includes('完成')) {
|
||||||
|
return '<div class="log-line log-success">' + escapeHtml(line) + '</div>';
|
||||||
|
} else {
|
||||||
|
return '<div class="log-line">' + escapeHtml(line) + '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshLogs() {
|
||||||
|
fetch('/api/logs')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const container = document.getElementById('logContainer');
|
||||||
|
if (data.logs && data.logs.length > 0) {
|
||||||
|
container.innerHTML = data.logs.map(formatLogLine).join('');
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
} else {
|
||||||
|
container.innerHTML = '<div class="loading">暂无日志</div>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('获取日志失败:', error);
|
||||||
|
document.getElementById('logContainer').innerHTML =
|
||||||
|
'<div class="log-line log-error">获取日志失败: ' + error.message + '</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDisplay() {
|
||||||
|
document.getElementById('logContainer').innerHTML = '<div class="loading">显示已清空,点击刷新重新加载</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAutoRefresh() {
|
||||||
|
isAutoRefresh = !isAutoRefresh;
|
||||||
|
const text = document.getElementById('autoRefreshText');
|
||||||
|
|
||||||
|
if (isAutoRefresh) {
|
||||||
|
text.textContent = '关闭自动刷新';
|
||||||
|
autoRefreshInterval = setInterval(refreshLogs, 3000);
|
||||||
|
refreshLogs();
|
||||||
|
} else {
|
||||||
|
text.textContent = '开启自动刷新';
|
||||||
|
if (autoRefreshInterval) {
|
||||||
|
clearInterval(autoRefreshInterval);
|
||||||
|
autoRefreshInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取日志
|
||||||
|
refreshLogs();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
@self.app.route('/api/logs')
|
||||||
|
def get_logs():
|
||||||
|
"""获取日志内容API"""
|
||||||
|
try:
|
||||||
|
log_file = self.monitor.config.get('logging', {}).get('file', '.devops/logs/devops.log')
|
||||||
|
log_path = Path(log_file)
|
||||||
|
|
||||||
|
if not log_path.exists():
|
||||||
|
return jsonify({'logs': [], 'message': '日志文件不存在'})
|
||||||
|
|
||||||
|
# 读取最后1000行日志
|
||||||
|
with open(log_path, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
# 只返回最后1000行
|
||||||
|
recent_lines = lines[-1000:] if len(lines) > 1000 else lines
|
||||||
|
return jsonify({'logs': [line.rstrip() for line in recent_lines]})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'logs': [], 'error': str(e)})
|
||||||
|
|
||||||
|
@self.app.route('/<project_name>')
|
||||||
|
def deploy_project(project_name):
|
||||||
|
"""触发项目部署"""
|
||||||
|
# 避免与 /logs 路由冲突
|
||||||
|
if project_name == 'logs' or project_name == 'api':
|
||||||
|
return jsonify({'code': 404, 'message': '路径不存在'})
|
||||||
|
|
||||||
|
Logger.info(f"收到HTTP部署请求: {project_name}")
|
||||||
|
|
||||||
|
# 在后台线程中执行部署
|
||||||
|
def deploy_task():
|
||||||
|
success, message = self.monitor.deploy_by_name(project_name)
|
||||||
|
Logger.info(f"部署结果: {message}")
|
||||||
|
|
||||||
|
thread = threading.Thread(target=deploy_task)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'code': 200,
|
||||||
|
'message': f'已触发 {project_name} 部署,请查看日志了解部署进度',
|
||||||
|
'project': project_name
|
||||||
|
})
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""启动HTTP服务器"""
|
||||||
|
Logger.info(f"HTTP部署服务器启动在端口 {self.port}")
|
||||||
|
self.app.run(host='0.0.0.0', port=self.port, debug=False, use_reloader=False)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数"""
|
"""主函数"""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
@ -682,6 +1100,8 @@ def main():
|
||||||
parser = argparse.ArgumentParser(description='Git 仓库监听器')
|
parser = argparse.ArgumentParser(description='Git 仓库监听器')
|
||||||
parser.add_argument('--config', default='.devops/config.yaml', help='配置文件路径')
|
parser.add_argument('--config', default='.devops/config.yaml', help='配置文件路径')
|
||||||
parser.add_argument('--once', action='store_true', help='只执行一次检查')
|
parser.add_argument('--once', action='store_true', help='只执行一次检查')
|
||||||
|
parser.add_argument('--http-port', type=int, default=9999, help='HTTP服务器端口(默认9999)')
|
||||||
|
parser.add_argument('--no-http', action='store_true', help='禁用HTTP服务器')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
@ -690,6 +1110,15 @@ def main():
|
||||||
if args.once:
|
if args.once:
|
||||||
monitor.run_once()
|
monitor.run_once()
|
||||||
else:
|
else:
|
||||||
|
# 启动HTTP服务器(在单独的线程中)
|
||||||
|
if not args.no_http:
|
||||||
|
server = DeploymentServer(monitor, port=args.http_port)
|
||||||
|
http_thread = threading.Thread(target=server.run)
|
||||||
|
http_thread.daemon = True
|
||||||
|
http_thread.start()
|
||||||
|
Logger.info(f"✓ HTTP部署服务器已启动: http://0.0.0.0:{args.http_port}")
|
||||||
|
|
||||||
|
# 启动Git监听
|
||||||
monitor.run()
|
monitor.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@ if ! python3 -c "import yaml" 2>/dev/null; then
|
||||||
echo "安装 PyYAML..."
|
echo "安装 PyYAML..."
|
||||||
pip3 install --user PyYAML
|
pip3 install --user PyYAML
|
||||||
fi
|
fi
|
||||||
|
if ! python3 -c "import flask" 2>/dev/null; then
|
||||||
|
echo "安装 Flask..."
|
||||||
|
pip3 install --user flask
|
||||||
|
fi
|
||||||
echo "✓ Python 依赖检查完成"
|
echo "✓ Python 依赖检查完成"
|
||||||
|
|
||||||
# 5. 删除已存在的 PM2 进程
|
# 5. 删除已存在的 PM2 进程
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,20 @@ if [ -z "$VIRTUAL_ENV" ]; then
|
||||||
echo "警告: 未找到 PyYAML,尝试安装..."
|
echo "警告: 未找到 PyYAML,尝试安装..."
|
||||||
pip3 install --user PyYAML || echo "请手动安装: pip3 install --user PyYAML"
|
pip3 install --user PyYAML || echo "请手动安装: pip3 install --user PyYAML"
|
||||||
fi
|
fi
|
||||||
|
if ! python3 -c "import flask" 2>/dev/null; then
|
||||||
|
echo "警告: 未找到 Flask,尝试安装..."
|
||||||
|
pip3 install --user flask || echo "请手动安装: pip3 install --user flask"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "检测到虚拟环境: $VIRTUAL_ENV"
|
echo "检测到虚拟环境: $VIRTUAL_ENV"
|
||||||
if ! python3 -c "import yaml" 2>/dev/null; then
|
if ! python3 -c "import yaml" 2>/dev/null; then
|
||||||
echo "安装 PyYAML..."
|
echo "安装 PyYAML..."
|
||||||
pip install PyYAML
|
pip install PyYAML
|
||||||
fi
|
fi
|
||||||
|
if ! python3 -c "import flask" 2>/dev/null; then
|
||||||
|
echo "安装 Flask..."
|
||||||
|
pip install flask
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 进入项目根目录
|
# 进入项目根目录
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue