使用 webhook 自动更新代码

最近在部署代码时,每次推送后都需要再登陆机器手动执行git pull更新代码,一两次还行,当重复多了之后,就觉得实在是太麻烦了,所以就研究了一下 webhook 功能,我们以 Gitee 为例简要说明一下其原理。

webhook 就是在仓库接收到一次事件(如 push、pull 等)后,向指定 URL 发送一次请求,因此,我们就可以在主机上部署脚本来处理收到这样的时间通知的动作。

以下是 Gitee 的 PUSH 事件推送的数据格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
{
"hook_name": "push_hooks",
"password": "pwd",
"hook_id": 1,
"hook_url": "http://gitee.com/liwen/gitos/hooks/1/edit",
"timestamp": "1576754827988",
"sign": "rLEHLuZRIQHuTPeXMib9Czoq9dVXO4TsQcmQQHtjXHA=",
"ref": "refs/heads/change_commitlint_config",
"before": "0000000000000000000000000000000000000000",
"after": "1cdcd819599cbb4099289dbbec762452f006cb40",
"created": true,
"deleted": false,
"compare": "https://gitee.com/oschina/gitee/compare/0000000000000000000000000000000000000000...1cdcd819599cbb4099289dbbec762452f006cb40",
"commits": [
{
"id": "1cdcd819599cbb4099289dbbec762452f006cb40",
"tree_id": "db78f3594ec0683f5d857ef731df0d860f14f2b2",
"distinct": true,
"message": "Update README.md",
"timestamp": "2018-02-05T23:46:46+08:00",
"url": "https://gitee.com/oschina/gitee/commit/1cdcd819599cbb4099289dbbec762452f006cb40",
"author": {
"time": "2018-02-05T23:46:46+08:00",
"name": "robot",
"email": "[email protected]",
"username": "robot",
"user_name": "robot",
"url": "https://gitee.com/robot"
},
"committer": {
"name": "robot",
"email": "[email protected]",
"username": "robot",
"user_name": "robot",
"url": "https://gitee.com/robot"
},
"added": null,
"removed": null,
"modified": [
"README.md"
]
}
],
"head_commit": {
"id": "1cdcd819599cbb4099289dbbec762452f006cb40",
"tree_id": "db78f3594ec0683f5d857ef731df0d860f14f2b2",
"distinct": true,
"message": "Update README.md",
"timestamp": "2018-02-05T23:46:46+08:00",
"url": "https://gitee.com/oschina/gitee/commit/1cdcd819599cbb4099289dbbec762452f006cb40",
"author": {
"time": "2018-02-05T23:46:46+08:00",
"name": "robot",
"email": "[email protected]",
"username": "robot",
"user_name": "robot",
"url": "https://gitee.com/robot"
},
"committer": {
"name": "robot",
"email": "[email protected]",
"username": "robot",
"user_name": "robot",
"url": "https://gitee.com/robot"
},
"added": null,
"removed": null,
"modified": [
"README.md"
]
},
"total_commits_count": 0,
"commits_more_than_ten": false,
"repository": {
"id": 120249025,
"name": "Gitee",
"path": "gitee",
"full_name": "开源中国/Gitee",
"owner": {
"id": 1,
"login": "robot",
"avatar_url": "https://gitee.com/assets/favicon.ico",
"html_url": "https://gitee.com/robot",
"type": "User",
"site_admin": false,
"name": "robot",
"email": "[email protected]",
"username": "robot",
"user_name": "robot",
"url": "https://gitee.com/robot"
},
"private": false,
"html_url": "https://gitee.com/oschina/gitee",
"url": "https://gitee.com/oschina/gitee",
"description": "",
"fork": false,
"created_at": "2018-02-05T23:46:46+08:00",
"updated_at": "2018-02-05T23:46:46+08:00",
"pushed_at": "2018-02-05T23:46:46+08:00",
"git_url": "git://gitee.com:oschina/gitee.git",
"ssh_url": "[email protected]:oschina/gitee.git",
"clone_url": "https://gitee.com/oschina/gitee.git",
"svn_url": "svn://gitee.com/oschina/gitee",
"git_http_url": "https://gitee.com/oschina/gitee.git",
"git_ssh_url": "[email protected]:oschina/gitee.git",
"git_svn_url": "svn://gitee.com/oschina/gitee",
"homepage": null,
"stargazers_count": 11,
"watchers_count": 12,
"forks_count": 0,
"language": "ruby",
"has_issues": true,
"has_wiki": true,
"has_pages": false,
"license": null,
"open_issues_count": 0,
"default_branch": "master",
"namespace": "oschina",
"name_with_namespace": "开源中国/Gitee",
"path_with_namespace": "oschina/gitee"
},
"sender": {
"id": 1,
"login": "robot",
"avatar_url": "https://gitee.com/assets/favicon.ico",
"html_url": "https://gitee.com/robot",
"type": "User",
"site_admin": false,
"name": "robot",
"email": "[email protected]",
"username": "robot",
"user_name": "robot",
"url": "https://gitee.com/robot"
},
"enterprise": {
"name": "开源中国",
"url": "https://gitee.com/oschina"
}
}

我们以 Laravel 代码为例来处理这个请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class GitHookController extends Controller
{
const TEST = [
'DIR' => '/home/wwwroot/demo/',
'BRANCH' => 'test'
];

const PROD = [
'DIR' => '/home/wwwroot/demo/',
'BRANCH' => 'master'
];

/**
* @param Request $request
*/
public function trigger(Request $request)
{
if ($request->input('total_commits_count') > 0) {
$log = [
'commits_count' => $request->input('total_commits_count'),
'commits' => array_column($request->input('commits'), 'id')
];

if ('refs/heads/master' === $request->input('ref') && 'production' === getenv('APP_ENV')) {
return $this->pullCode(self::PROD['DIR'], self::PROD['BRANCH'], $log);
} elseif ('refs/heads/test' === $request->input('ref') && 'test' === getenv('APP_ENV')) {
return $this->pullCode(self::TEST['DIR'], self::TEST['BRANCH'], $log);
}
} else {
return 'Nothing to update';
}
}

/**
* @param string $dir 代码根路径
* @param string $branch 分支名称
* @param array $log 日志内容
* @return string|null
*/
private function pullCode($dir, $branch, $log)
{
Log::channel('pullcode')->info('-------PULL START-------');

$result = shell_exec("cd {$dir} && git checkout . && git checkout {$branch} && git pull 2>&1");

Log::channel('pullcode')->info(json_encode($log));
Log::channel('pullcode')->info($result);
Log::channel('pullcode')->info('-------PULL END-------' . PHP_EOL);

return $result;
}
}

在调试过程中,遇到了一个难点,我登陆的用户是 admin,但脚本执行用户是非登录用户 www,admin 可以拉取代码,但我需要用 www 来执行脚本,这样就导致了 www 没有权限拉取代码,在用 www 用户执行shell_exec("git pull")等命令时没有任何的输出,经过一番查询后,加上2>&1才将输出打印,然后根据错误提示,设置 ssh、文件权限,最后终于成功了。

在设置 ssh 时,可以为 www 用户生成一套密钥对,也可以将 admin 的 ssh 复制到 www 的 .ssh 中;在文件权限这里,可以将 www 用户加入 admin 用户组,然后对代码中的所有文件都添加所属组的写入权限来解决sudo chmod -R g+w code

Reference

  1. php执行sh文件没有输出的原因及解决方法 - CSDN
  2. git webhooks 实现自动拉取代码
因为热爱,所以执着。