gogs_runner/main.go

292 lines
6.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
"regexp"
)
const (
path = "/"
healthCheckPath = "/health"
logDir = ".log/"
perm = 0770
)
var (
configs = []Config{}
configsLock = &sync.RWMutex{}
repositoryPath = func () string {
if os.Getenv("GOGS_REPOSITORY") != "" {
return os.Getenv("GOGS_REPOSITORY")
}
return os.Getenv("HOME") + "/repository/"
}()
logFile *os.File
)
func init() {
go func() {
for {
os.Mkdir(logDir, perm)
file := ".log/" + time.Now().Format("2006-01-02") + ".txt"
var err error
logFile, err = os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, perm)
if err != nil {
panic(err)
}
if os.Getenv("GOGS_REPOSITORY") != "" {
mw := io.MultiWriter(os.Stdout,logFile)
log.SetOutput(mw)
} else {
log.SetOutput(logFile)
}
time.Sleep(time.Minute)
}
}()
go func() {
for {
data, err := ioutil.ReadFile("config.json")
if err != nil {
return
}
configsLock.Lock()
if err := json.Unmarshal(data, &configs); err != nil {
log.Printf("Error unmarshalling configs: %v\n", err)
}
configsLock.Unlock()
time.Sleep(time.Second * 10)
}
}()
}
func getBranch(str string) (string) {
r, _ := regexp.Compile("refs/heads/(.*)")
matchArr := r.FindStringSubmatch(str)
if len(matchArr) > 0 {
return matchArr[len(matchArr)-1]
}
return ""
}
func runCommand(script string, projectPath string, isFile bool) (error) {
log.Printf("Running script: %s\n", script)
var cmd *exec.Cmd
if (isFile) {
cmd = exec.Command(script)
} else {
cmd = exec.Command("/bin/bash", "-c", script)
}
cmd.Stdout = logFile
cmd.Stderr = logFile
cmd.Dir = projectPath
err := cmd.Run()
return err
}
func manageProj(projectPath string, payload GogsPayload) (error) {
// 如果没有这个文件夹就现clone下来
if _, err := os.Stat(projectPath); os.IsNotExist(err) {
log.Printf("%s is not exist, start clone", projectPath)
// 创建文件夹
os.Mkdir(projectPath, os.ModePerm)
// 给文件夹权限
os.Chmod(projectPath, os.ModePerm)
// clone 到指定文件夹
script := "git clone " + payload.Repository.SSHURL + " " + projectPath
runErr := runCommand(script, projectPath, false)
if runErr != nil {
return runErr
}
}
// 切换到指定的分支
branch := getBranch(payload.Ref)
script := "git checkout " + branch
runErr := runCommand(script, projectPath, false)
if runErr != nil {
return runErr
}
// 拉取代码
script = "git pull --rebase"
runErr = runCommand(script, projectPath, false)
if runErr != nil {
return runErr
}
// 切换到指定的commit
script = "git reset --hard " + payload.After
runErr = runCommand(script, projectPath, false)
if runErr != nil {
return runErr
}
return nil
}
func manageDeploy(scriptPath string, projectPath string) (error) {
// 如果用户是Root就给部署文件添加执行权限
if os.Getenv("HOME") == "/root" {
log.Printf("Cur user is root, chmod u=rwx to %s", scriptPath)
script := "chmod u=rwx " + scriptPath
runErr := runCommand(script, projectPath, false)
if runErr != nil {
return runErr
}
}
// 开始执行脚本
runErr := runCommand(scriptPath, projectPath, true)
return runErr
}
func checkNeedDeploy(payload GogsPayload) (Config, bool) {
// 我们这个系统简单点只要本次提交包含了部署信号就直接部署最后一个commit
config := Config{
Repo: "",
Path: repositoryPath,
Script: "deploy.py",
Signal: "{D}",
Branch: "",
}
// 找到指定的项目,获取用户配置
for _, item := range configs {
if item.Repo == payload.Repository.FullName {
config.Repo = item.Repo
if item.Path != "" {
config.Path = item.Path
}
if item.Script != "" {
config.Script = item.Script
}
if item.Signal != "" {
config.Signal = item.Signal
}
if item.Branch != "" {
config.Branch = item.Branch
}
break
}
}
// 检查是否需要执行部署脚本,优先分支,然后是推送信息
// 分支匹配
if config.Branch != "" && config.Branch == getBranch(payload.Ref) {
return config, true
}
// 推送信息匹配
for _, commit := range payload.Commits {
if strings.Contains(commit.Message, config.Signal) {
return config, true
}
}
return config, false
}
func main() {
addr := flag.String("a", ":3001", "Address to listen on")
flag.Parse()
http.HandleFunc(healthCheckPath, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("I'm alive"))
return
})
http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
// 获取请求体
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading request body: %v\n", err)
w.Write([]byte(fmt.Sprintf("Read body error: %s", err)))
return
}
// 解析payload
var payload GogsPayload
err = json.Unmarshal(reqBody, &payload)
if err != nil {
log.Printf("Error unmarshalling request body: %v\n", err)
w.Write([]byte(fmt.Sprintf("Unmarshal body error: %s", err)))
return
}
// 打印代码库名称以及推送者
log.Printf("Received: [Repository] %s [Sender] %s", payload.Repository.FullName, payload.Sender.Username)
// 检查是否需要部署
deployConfig, needDeploy := checkNeedDeploy(payload)
// 不需要部署就直接返回
if (!needDeploy) {
log.Print("No deploy commit found\n\n")
w.Write([]byte("No deploy commit found. Skip."))
return
}
// 准备部署,生成项目地址和脚本地址
projectPath := func() string {
hasPrefix := strings.HasPrefix(deployConfig.Path, "/")
hasSuffix := strings.HasSuffix(deployConfig.Path, "/")
base := ""
// 如果路径以/开头,则认为是绝对路径
if hasPrefix {
base = deployConfig.Path
} else {
base = repositoryPath + deployConfig.Path
}
repoFmt := ""
if hasSuffix {
repoFmt = "%s/"
} else {
repoFmt = "/%s/"
}
return fmt.Sprintf(base+repoFmt, payload.Repository.Name)
}()
scriptPath := projectPath + deployConfig.Script
// 处理项目clone并切换到指定的分支和commit
err = manageProj(projectPath, payload)
if err != nil {
log.Printf("Error running manageProj script: %v\n", err)
w.Write([]byte(fmt.Sprintf("manageProj script error: %s", err)))
return
}
// 给脚本添加权限并执行脚本
err = manageDeploy(scriptPath, projectPath)
if err != nil {
log.Printf("Error running deploy script: %v\n", err)
w.Write([]byte(fmt.Sprintf("Deploy script error: %s", err)))
} else {
log.Print("Deploy finished\n\n")
w.Write([]byte("Deploy finished"))
}
return
})
log.Println("Webhook service started at " + *addr)
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Println("Error starting webhook service: " + err.Error())
}
}