feat: 工具配置增强、站点拨测与日志中心多项改进
- 工具配置:新增卸载 API/UI、Go 工具链、流水线构建自动安装依赖 - 修复 ListTools VerifyTool 递归栈溢出导致获取工具配置失败 - Node.js Alpine 安装改用 unofficial-builds musl 预编译包与 index.json 解析 LTS - 站点拨测:详情页路由、告警自动恢复、并发拨测与默认可观测频率 - 日志中心:RBAC、查询构建器、ES/Loki 增强与审计 request/response 展示 - 发布配置:构建节点、流水线远程执行、Deploy RBAC 导航拆分
This commit is contained in:
1 parent
cde7bbe540
commit
7e1c8f583c
84 files changed
+7729
-1507
No files matched your search
@@ -0,0 +1,193 @@
|
||||
package toolinstaller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"ezops/model"
|
||||
)
|
||||
|
||||
// UninstallResult 工具卸载结果(含脚本输出,供审计日志)// Logan.Li
|
||||
type UninstallResult struct {
|
||||
Output string
|
||||
Err error
|
||||
}
|
||||
|
||||
// Uninstall 卸载指定工具并重置 DB 状态(允许重新安装)// Logan.Li
|
||||
func (ti *ToolInstaller) Uninstall(toolName string) UninstallResult {
|
||||
var tool model.DeployTool
|
||||
if err := ti.db.Where("name = ?", toolName).First(&tool).Error; err != nil {
|
||||
return UninstallResult{Err: fmt.Errorf("tool %s not found in DB: %w", toolName, err)}
|
||||
}
|
||||
|
||||
ti.mu.Lock()
|
||||
delete(ti.onces, toolName)
|
||||
ti.mu.Unlock()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := ti.uninstallTool(&tool, &buf); err != nil {
|
||||
out := strings.TrimSpace(buf.String())
|
||||
log.Printf("[ToolInstaller] ERROR uninstalling %s: %v", toolName, err)
|
||||
ti.resetUninstallStatus(tool.ID, out+": "+err.Error())
|
||||
return UninstallResult{Output: out, Err: err}
|
||||
}
|
||||
|
||||
ti.resetUninstallStatus(tool.ID, "")
|
||||
out := strings.TrimSpace(buf.String())
|
||||
log.Printf("[ToolInstaller] %s uninstalled successfully", toolName)
|
||||
return UninstallResult{Output: out}
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) resetUninstallStatus(toolID uint, errMsg string) {
|
||||
now := time.Now()
|
||||
updates := map[string]interface{}{
|
||||
"status": model.ToolNotInstalled,
|
||||
"installed_ver": "",
|
||||
"last_check": &now,
|
||||
}
|
||||
if errMsg != "" {
|
||||
updates["error_msg"] = errMsg
|
||||
} else {
|
||||
updates["error_msg"] = ""
|
||||
}
|
||||
ti.updateStatusMap(toolID, updates)
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) updateStatusMap(toolID uint, updates map[string]interface{}) {
|
||||
ti.db.Model(&model.DeployTool{}).Where("id = ?", toolID).Updates(updates)
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallTool(tool *model.DeployTool, buf *bytes.Buffer) error {
|
||||
switch tool.Name {
|
||||
case "jdk":
|
||||
return ti.uninstallJDK(buf)
|
||||
case "maven":
|
||||
return ti.uninstallMaven(buf)
|
||||
case "golang":
|
||||
return ti.uninstallGolang(buf)
|
||||
case "nvm":
|
||||
return ti.uninstallNVM(buf)
|
||||
case "nodejs":
|
||||
return ti.uninstallNodeJS(buf)
|
||||
case "git":
|
||||
return ti.uninstallApkPackage(buf, "git", "git")
|
||||
case "docker-cli":
|
||||
return ti.uninstallApkPackage(buf, "docker-cli", "docker")
|
||||
default:
|
||||
return ti.uninstallBinary(tool, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallJDK(buf *bytes.Buffer) error {
|
||||
buf.WriteString("[JDK] Removing symlinks and /usr/local/share/jdk\n")
|
||||
removeIfExists(buf, "/usr/local/bin/java", "/usr/local/bin/javac", "/usr/local/bin/jar")
|
||||
removeDirIfExists(buf, "/usr/local/share/jdk")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallMaven(buf *bytes.Buffer) error {
|
||||
buf.WriteString("[Maven] Removing symlinks and /usr/local/share/maven\n")
|
||||
removeIfExists(buf, "/usr/local/bin/mvn")
|
||||
removeDirIfExists(buf, "/usr/local/share/maven")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallGolang(buf *bytes.Buffer) error {
|
||||
buf.WriteString("[Go] Removing GOROOT and symlinks\n")
|
||||
removeIfExists(buf, "/usr/local/bin/go", "/usr/local/bin/gofmt", "/etc/profile.d/golang.sh")
|
||||
removeDirIfExists(buf, "/usr/local/go")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallNVM(buf *bytes.Buffer) error {
|
||||
buf.WriteString("[NVM] Removing NVM and Node versions\n")
|
||||
removeIfExists(buf, "/usr/local/bin/node", "/usr/local/bin/npm", "/usr/local/bin/npx", "/etc/profile.d/nvm.sh")
|
||||
removeDirIfExists(buf, "/usr/local/share/nvm")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallNodeJS(buf *bytes.Buffer) error {
|
||||
buf.WriteString("[Node.js] Removing symlinks and NVM node versions\n")
|
||||
removeIfExists(buf, "/usr/local/bin/node", "/usr/local/bin/npm", "/usr/local/bin/npx")
|
||||
|
||||
nvmSh := "/usr/local/share/nvm/nvm.sh"
|
||||
if st, err := os.Stat(nvmSh); err == nil && !st.IsDir() {
|
||||
script := fmt.Sprintf(`export NVM_DIR="/usr/local/share/nvm"
|
||||
. "%s"
|
||||
CUR=$(nvm current 2>/dev/null || true)
|
||||
if [ -n "$CUR" ] && [ "$CUR" != "none" ] && [ "$CUR" != "system" ]; then
|
||||
nvm uninstall "$CUR" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$NVM_DIR/versions/node"
|
||||
`, nvmSh)
|
||||
cmd := exec.Command("bash", "-c", script)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if len(out) > 0 {
|
||||
buf.Write(out)
|
||||
}
|
||||
if err != nil {
|
||||
buf.WriteString(fmt.Sprintf("[Node.js] nvm cleanup warning: %v\n", err))
|
||||
}
|
||||
}
|
||||
removeDirIfExists(buf, "/usr/local/share/nvm/versions/node")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallApkPackage(buf *bytes.Buffer, pkg, binary string) error {
|
||||
buf.WriteString(fmt.Sprintf("[%s] Trying apk del %s\n", binary, pkg))
|
||||
if _, err := exec.LookPath("apk"); err != nil {
|
||||
buf.WriteString(fmt.Sprintf("[%s] apk not found, skip package removal\n", binary))
|
||||
return nil
|
||||
}
|
||||
cmd := exec.Command("apk", "del", "--no-cache", pkg)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if len(out) > 0 {
|
||||
buf.Write(out)
|
||||
}
|
||||
if err != nil {
|
||||
// 可能未通过 apk 安装,仅标记卸载完成 // Logan.Li
|
||||
buf.WriteString(fmt.Sprintf("[%s] apk del skipped: %v\n", binary, err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *ToolInstaller) uninstallBinary(tool *model.DeployTool, buf *bytes.Buffer) error {
|
||||
path := tool.InstallPath
|
||||
if path == "" && tool.BinaryName != "" {
|
||||
path = filepath.Join("/usr/local/bin", tool.BinaryName)
|
||||
}
|
||||
if path == "" {
|
||||
return fmt.Errorf("no install path configured for %s", tool.Name)
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("[%s] Removing %s\n", tool.Name, path))
|
||||
removeIfExists(buf, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeIfExists(buf *bytes.Buffer, paths ...string) {
|
||||
for _, p := range paths {
|
||||
if err := os.Remove(p); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
buf.WriteString(fmt.Sprintf("remove %s: %v\n", p, err))
|
||||
}
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("removed %s\n", p))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeDirIfExists(buf *bytes.Buffer, dir string) {
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
buf.WriteString(fmt.Sprintf("remove dir %s: %v\n", dir, err))
|
||||
}
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("removed dir %s\n", dir))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user