281 lines
7.3 KiB
Go
281 lines
7.3 KiB
Go
// main.go
|
||
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// BuildRequest represents a build job in the queue
|
||
type BuildRequest struct {
|
||
RepoURL string
|
||
Branch string
|
||
}
|
||
|
||
// BuildQueue manages the build requests
|
||
type BuildQueue struct {
|
||
queue []BuildRequest
|
||
mu sync.Mutex
|
||
}
|
||
|
||
var buildQueue = &BuildQueue{}
|
||
|
||
// Add adds a new build request to the queue
|
||
func (bq *BuildQueue) Add(req BuildRequest) {
|
||
bq.mu.Lock()
|
||
defer bq.mu.Unlock()
|
||
bq.queue = append(bq.queue, req)
|
||
go bq.processQueue() // Start processing if not already running
|
||
}
|
||
|
||
// processQueue handles the build requests one at a time
|
||
func (bq *BuildQueue) processQueue() {
|
||
bq.mu.Lock()
|
||
if len(bq.queue) == 0 {
|
||
bq.mu.Unlock()
|
||
return
|
||
}
|
||
|
||
// Get the next request
|
||
req := bq.queue[0]
|
||
queueLength := len(bq.queue) - 1 // 减去当前正在处理的请求
|
||
bq.queue = bq.queue[1:]
|
||
bq.mu.Unlock()
|
||
|
||
// 发送准备构建通知
|
||
webhookURL := "https://open.feishu.cn/open-apis/bot/v2/hook/8648822c-fbf8-45ba-bd4d-907c2abdb885"
|
||
if err := sendFeishuNotification(webhookURL, &req, "", "prepare", queueLength); err != nil {
|
||
log.Printf("发送准备构建通知失败: %v", err)
|
||
}
|
||
|
||
// Process the build request
|
||
err := processBuild(&req)
|
||
if err != nil {
|
||
log.Printf("Error processing build: %v", err)
|
||
}
|
||
|
||
// Process next item in queue
|
||
go bq.processQueue()
|
||
}
|
||
|
||
func processBuild(req *BuildRequest) error {
|
||
log.Printf("开始处理构建请求 - 仓库: %s, 分支: %s", req.RepoURL, req.Branch)
|
||
|
||
// 发送开始构建通知
|
||
webhookURL := "https://open.feishu.cn/open-apis/bot/v2/hook/8648822c-fbf8-45ba-bd4d-907c2abdb885"
|
||
if err := sendFeishuNotification(webhookURL, req, "", "start", 0); err != nil {
|
||
log.Printf("发送开始构建通知失败: %v", err)
|
||
}
|
||
|
||
// Create a temporary directory for the build
|
||
buildDir, err := os.MkdirTemp("", "build-*")
|
||
if err != nil {
|
||
return fmt.Errorf("创建构建目录失败: %v", err)
|
||
}
|
||
log.Printf("创建临时构建目录: %s", buildDir)
|
||
defer func() {
|
||
log.Printf("清理临时构建目录: %s", buildDir)
|
||
os.RemoveAll(buildDir)
|
||
}()
|
||
|
||
// Clone the repository
|
||
log.Printf("开始克隆仓库...")
|
||
cloneCmd := exec.Command("git", "clone", "-b", req.Branch, req.RepoURL, buildDir)
|
||
output, err := cloneCmd.CombinedOutput()
|
||
if err != nil {
|
||
return fmt.Errorf("git clone失败:\n命令输出: %s\n错误: %v", string(output), err)
|
||
}
|
||
log.Printf("仓库克隆成功")
|
||
|
||
// 获取版本号
|
||
log.Printf("获取版本号...")
|
||
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0", "--exact-match", "HEAD")
|
||
cmd.Dir = buildDir
|
||
version, err := cmd.Output()
|
||
if err != nil {
|
||
// 如果没有tag,则使用commit id
|
||
cmd = exec.Command("git", "rev-parse", "--short", "HEAD")
|
||
cmd.Dir = buildDir
|
||
version, err = cmd.Output()
|
||
if err != nil {
|
||
return fmt.Errorf("获取commit id失败: %v", err)
|
||
}
|
||
}
|
||
versionStr := strings.TrimSpace(string(version))
|
||
log.Printf("版本号: %s", versionStr)
|
||
|
||
// Run make deploy
|
||
log.Printf("开始执行make all命令...")
|
||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||
defer cancel()
|
||
|
||
makeCmd := exec.CommandContext(ctx, "make")
|
||
makeCmd.Dir = buildDir
|
||
|
||
// 直接设置标准输出和错误输出
|
||
makeCmd.Stdout = os.Stdout
|
||
makeCmd.Stderr = os.Stderr
|
||
|
||
log.Printf("正在启动make 命令...")
|
||
if err := makeCmd.Start(); err != nil {
|
||
return fmt.Errorf("启动make命令失败: %v", err)
|
||
}
|
||
log.Printf("make all命令已启动,开始执行...")
|
||
|
||
// 等待命令完成
|
||
if err := makeCmd.Wait(); err != nil {
|
||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||
return fmt.Errorf("命令执行失败,退出码: %d", exitErr.ExitCode())
|
||
}
|
||
return fmt.Errorf("make命令执行失败: %v", err)
|
||
}
|
||
|
||
if ctx.Err() == context.DeadlineExceeded {
|
||
return fmt.Errorf("命令执行超时")
|
||
}
|
||
|
||
log.Printf("cicd执行完成")
|
||
|
||
// 发送飞书通知
|
||
if err := sendFeishuNotification(webhookURL, req, versionStr, "success", 0); err != nil {
|
||
log.Printf("发送飞书通知失败: %v", err)
|
||
} else {
|
||
log.Printf("飞书通知发送成功")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// FeishuMessage 飞书消息结构
|
||
type FeishuMessage struct {
|
||
MsgType string `json:"msg_type"`
|
||
Content struct {
|
||
Text string `json:"text"`
|
||
} `json:"content"`
|
||
}
|
||
|
||
// sendFeishuNotification 发送飞书通知
|
||
func sendFeishuNotification(webhook string, request *BuildRequest, version string, msgType string, queueLength int) error {
|
||
var messageText string
|
||
switch msgType {
|
||
case "prepare":
|
||
waitTime := queueLength * 3 // 假设每个构建大约需要3分钟
|
||
messageText = fmt.Sprintf("⌛ 构建请求已收到\n"+
|
||
"───────────────\n"+
|
||
"📦 项目: %s\n"+
|
||
"🌿 分支: %s\n"+
|
||
"📊 队列状态: 前面还有 %d 个构建请求\n"+
|
||
"⏳ 预计等待: 约 %d 分钟\n"+
|
||
"───────────────\n"+
|
||
"请耐心等待...",
|
||
request.RepoURL,
|
||
request.Branch,
|
||
queueLength,
|
||
waitTime)
|
||
case "start":
|
||
messageText = fmt.Sprintf("🏗️ 开始构建\n"+
|
||
"───────────────\n"+
|
||
"📦 项目: %s\n"+
|
||
"🌿 分支: %s\n"+
|
||
"🕐 时间: %s\n"+
|
||
"───────────────\n"+
|
||
"构建进行中...",
|
||
request.RepoURL,
|
||
request.Branch,
|
||
time.Now().Format("2006-01-02 15:04:05"))
|
||
case "success":
|
||
messageText = fmt.Sprintf("🚀 部署通知\n"+
|
||
"───────────────\n"+
|
||
"📦 项目: %s\n"+
|
||
"🌿 分支: %s\n"+
|
||
"🏷️ 版本: %s\n"+
|
||
"✨ 状态: 部署成功\n"+
|
||
"🕐 时间: %s\n"+
|
||
"───────────────\n"+
|
||
"祝您工作愉快!👨💻",
|
||
request.RepoURL,
|
||
request.Branch,
|
||
version,
|
||
time.Now().Format("2006-01-02 15:04:05"))
|
||
}
|
||
|
||
message := FeishuMessage{
|
||
MsgType: "text",
|
||
Content: struct {
|
||
Text string `json:"text"`
|
||
}{
|
||
Text: messageText,
|
||
},
|
||
}
|
||
|
||
jsonData, err := json.Marshal(message)
|
||
if err != nil {
|
||
return fmt.Errorf("序列化消息失败: %v", err)
|
||
}
|
||
|
||
resp, err := http.Post(webhook, "application/json", bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return fmt.Errorf("发送通知失败: %v", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
body, _ := io.ReadAll(resp.Body)
|
||
return fmt.Errorf("飞书API返回错误: status=%d, body=%s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func webhookHandler(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPost {
|
||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
|
||
var payload struct {
|
||
Repository struct {
|
||
GitHTTPURL string `json:"git_http_url"`
|
||
} `json:"repository"`
|
||
Ref string `json:"ref"`
|
||
}
|
||
|
||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||
http.Error(w, "Failed to parse webhook payload", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// Extract branch name from ref (refs/heads/master -> master)
|
||
branch := filepath.Base(payload.Ref)
|
||
|
||
// Add build request to queue
|
||
buildQueue.Add(BuildRequest{
|
||
RepoURL: payload.Repository.GitHTTPURL,
|
||
Branch: branch,
|
||
})
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
fmt.Fprintf(w, "Build request queued")
|
||
}
|
||
|
||
func main() {
|
||
http.HandleFunc("/webhook", webhookHandler)
|
||
|
||
port := "29999"
|
||
log.Printf("Starting server on port %s", port)
|
||
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
}
|