├── .travis.yml ├── README.md ├── webhook.conf └── webhook.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Github/Bitbucket webhook tools 2 | == 3 | 4 | [![Qiniu Logo](http://qiniutek.com/images/logo-2.png)](http://qiniu.com/) 5 | 6 | 基于 Github/Bitbucket webhook 做了个小工具。 7 | 8 | 它能够做什么?简单来说,它就是一个让 Github/Bitbucket repo 在某个分支发生 push 行为的时候,自动触发一段脚本。 9 | 10 | # 用法 11 | 12 | ``` 13 | go run webhook.go xxx.conf 14 | ``` 15 | 16 | 这样就启动 webhook 服务器了。其中 conf 文件格式如下: 17 | 18 | ``` 19 | { 20 | "bind": ":9876", 21 | "items": [ 22 | { 23 | "repo": "https://github.com/qiniu/docs.qiniu.com", 24 | "branch": "master", 25 | "script": "update-qiniu-docs.sh" 26 | }, 27 | { 28 | "repo": "https://bitbucket.org/Wuvist/angelbot/", 29 | "branch": "master", 30 | "script": "restart-angelbot.sh" 31 | } 32 | ]} 33 | ``` 34 | 35 | 这个样例是真实的。它设置了 1 个 hook 脚本,在 https://github.com/qiniu/docs.qiniu.com 这个 repo 的 master 有变化时,自动执行 update-qiniu-docs.sh 脚本。 36 | 37 | 38 | # 与 Github 关联 39 | 40 | 在你的 repo 首页(例如 https://github.com/qiniu/docs.qiniu.com ),点 Settings,再进入 Service Hooks,再进入 WebHook URLs,这里你就可以设置你的 WebHook URL 了,比如 http://example.com:9876/ 。 41 | 42 | 配置好后,再确定 webhook 已经启动,你就可以尝试向 repo push 一些修改,看看能不能执行相应的脚本了。 43 | -------------------------------------------------------------------------------- /webhook.conf: -------------------------------------------------------------------------------- 1 | { 2 | "bind": ":9876", 3 | "items": [ 4 | { 5 | "repo": "https://github.com/qiniu/docs.qiniu.com", 6 | "branch": "master", 7 | "script": "update-qiniu-docs.sh" 8 | } 9 | ]} 10 | -------------------------------------------------------------------------------- /webhook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | // -------------------------------------------------------------------------------- 14 | 15 | type WatchItem struct { 16 | Repo string `json:"repo"` 17 | Branch string `json:"branch"` 18 | Script string `json:"script"` 19 | } 20 | 21 | type Config struct { 22 | BindHost string `json:"bind"` 23 | Items []WatchItem `json:"items"` 24 | } 25 | 26 | // -------------------------------------------------------------------------------- 27 | 28 | type Repository struct { 29 | Url string `json:"url"` // "https://github.com/qiniu/api" 30 | AbsoluteUrl string `json:"absolute_url"` 31 | } 32 | 33 | type Commit struct { 34 | Branch string `json:"branch"` 35 | } 36 | 37 | type Payload struct { 38 | Ref string `json:"ref"` // "refs/heads/develop" 39 | Repo Repository `json:"repository"` 40 | CanonUrl string `json:"canon_url"` 41 | Commits []Commit `json:"commits"` 42 | } 43 | 44 | // -------------------------------------------------------------------------------- 45 | 46 | var cfg Config 47 | 48 | // -------------------------------------------------------------------------------- 49 | 50 | func runScript(item *WatchItem) (err error) { 51 | script := "./" + item.Script 52 | out, err := exec.Command("bash", "-c", script).Output() 53 | if err != nil { 54 | log.Printf("Exec command failed: %s\n", err) 55 | } 56 | 57 | log.Printf("Run %s output: %s\n", script, string(out)) 58 | return 59 | } 60 | 61 | func handleGithub(event Payload, cfg *Config) (err error) { 62 | for _, item := range cfg.Items { 63 | if event.Repo.Url == item.Repo && strings.Contains(event.Ref, item.Branch) { 64 | err = runScript(&item) 65 | if err != nil { 66 | log.Printf("run script error: %s\n", err) 67 | } 68 | break 69 | } 70 | } 71 | return 72 | } 73 | 74 | func handleBitbucket(event Payload, cfg *Config) { 75 | changingBranches := make(map[string]bool) 76 | 77 | for _, commit := range event.Commits { 78 | changingBranches[commit.Branch] = true 79 | } 80 | 81 | repo := strings.TrimRight(event.CanonUrl+event.Repo.AbsoluteUrl, "/") 82 | 83 | for _, item := range cfg.Items { 84 | if strings.TrimRight(item.Repo, "/") == repo && changingBranches[item.Branch] { 85 | runScript(&item) 86 | } 87 | } 88 | return 89 | } 90 | 91 | func handle(w http.ResponseWriter, req *http.Request) { 92 | defer req.Body.Close() 93 | decoder := json.NewDecoder(req.Body) 94 | 95 | var event Payload 96 | err := decoder.Decode(&event) 97 | if err != nil { 98 | log.Printf("payload json decode failed: %s\n", err) 99 | return 100 | } 101 | 102 | if event.CanonUrl == "https://bitbucket.org" { 103 | handleBitbucket(event, &cfg) 104 | return 105 | } 106 | 107 | handleGithub(event, &cfg) 108 | } 109 | 110 | // -------------------------------------------------------------------------------- 111 | 112 | func main() { 113 | 114 | if len(os.Args) < 2 { 115 | println("Usage: webhook \n") 116 | return 117 | } 118 | 119 | cfgbuf, err := ioutil.ReadFile(os.Args[1]) 120 | if err != nil { 121 | log.Println("Read config file failed:", err) 122 | return 123 | } 124 | 125 | err = json.Unmarshal(cfgbuf, &cfg) 126 | if err != nil { 127 | log.Println("Unmarshal config failed:", err) 128 | return 129 | } 130 | 131 | http.HandleFunc("/", handle) 132 | log.Fatal(http.ListenAndServe(cfg.BindHost, nil)) 133 | } 134 | 135 | // -------------------------------------------------------------------------------- 136 | --------------------------------------------------------------------------------