1.背景
原本持续构建一直使用Jenkins,近期服务器资源吃紧,停掉了很多服务,团队代码发布比较繁琐,随手写了这段程序进行自动发布。
编程语言选择go,因为体积小,开发快,服务占用资源少,最主要的是可以编译成二进制文件,运行比较方便。
2.依赖
除了yml配置解析库是第三方库,其他都是go原生库的API。
解析yml引用gopkg.in/yaml.v2,引用方式,命令行执行:
go get gopkg.in/yaml.v2
3.配置文件
配置文件配置要执行的脚本,url最后一段解析为key,value存储脚本实际路径:
hrms: f:/scripts/hrms.sh
hrms-web: f:/scripts/hrms-web.sh
hrms-h5: f:/scripts/hrms-h5.sh
4.go源码
package main
import (
"encoding/json"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
)
var (
Trace *log.Logger
Info *log.Logger
Warning *log.Logger
Error *log.Logger
)
func init() {
file, err := os.OpenFile("error.log",
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open error log file:", err)
}
Trace = log.New(ioutil.Discard,
"TRACE:",
log.Ldate|log.Ltime|log.Lshortfile)
Info = log.New(os.Stdout,
"INFO:",
log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(os.Stdout,
"WARNING:",
log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(io.MultiWriter(file, os.Stderr),
"ERROR:",
log.Ldate|log.Ltime|log.Lshortfile)
}
type PusherJson struct {
Pusher PusherModel `json:"pusher"`
}
type PusherModel struct {
Id int `json:"id"`
FullName string `json:"full_name"`
Username string `json:"username"`
}
var config map[string]string
var mutex sync.Mutex
func init() {
file, err := os.Open("config.yml")
if err != nil {
Error.Println("Failed to open config.yml resource")
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
Warning.Println("Close config file failed")
}
}(file)
configBuff, err := ioutil.ReadAll(file)
if err != nil {
Error.Println("Failed to read yaml config file")
return
}
err = yaml.Unmarshal(configBuff, &config)
if err != nil {
Error.Println("Parse yaml config failed:", err)
return
}
Info.Println("Load yaml config file success")
}
func hook(rw http.ResponseWriter, r *http.Request) {
Trace.Printf("received request, method:%v, url:%v", r.Method, r.URL)
if r.Method == "GET" {
Error.Printf("IGNORE GET REQUEST")
return
}
res := struct {
code int
msg string
data []string
}{
code: 200,
msg: "请求成功",
data: nil,
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
Error.Println("Read body error:", err)
}
var pusher PusherJson
if err = json.Unmarshal(body, &pusher); err != nil {
Error.Println("Unmarshal body error:", err)
return
}
requestUrl := r.URL.String()
if strings.Contains(requestUrl, "/hooks") {
index := strings.LastIndex(requestUrl, "/")
s := requestUrl[(index + 1):]
shellPath := config[s]
Info.Printf("Receive %s publish request, pusher is %s, git username:%s", s, pusher.Pusher.FullName, pusher.Pusher.Username)
mutex.Lock()
{
startTime := time.Now()
command := exec.Command("cmd.exe", "/c", shellPath)
err := command.Start()
if err != nil {
Error.Println("Start execute shell failed", err)
}
Info.Println("Start execute the shell, Process Pid:", command.Process.Pid)
err = command.Wait()
if err != nil {
Error.Println("Execute shell error:", err)
}
endTime := time.Now()
Info.Printf("Process execute success, PID:%v, cost:%v", command.ProcessState.Pid(), endTime.Sub(startTime))
}
mutex.Unlock()
}
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(200)
err = json.NewEncoder(rw).Encode(&res)
if err != nil {
Error.Println("response error:", err)
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", hook)
server := &http.Server{
Addr: "0.0.0.0:6606",
Handler: mux,
}
Info.Println("Start server listening at ", server.Addr)
err := server.ListenAndServe()
if err != nil {
Error.Println("start server failed:", err)
}
}
5.说明
这段go代码可以启动一个web服务,监听6606端口,根据请求url进行解析,执行配置好的脚本进行自动发布。
脚本具体发布方式,可以采用docker私仓的形式,将项目build成镜像后,push到私仓,服务器集群根据新镜像进行更新(配置免密登录)。