go实现git webhook持续构建服务自动发布程序

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到私仓,服务器集群根据新镜像进行更新(配置免密登录)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值