本文将介绍如何利用 Gitlab API 实现一套简单灵活的数据同步机制,从而实现在多个 Gitlab 站点间同步数据。
在继续写数学系列前,我想切回去之前的 Git 系列写点东西。我想写系列文章也可以像操作系统的进程调度一样,一个系列暂时写不动了,先 保存现场 跳去另一个 topic 写点东西,同时也给自己留点 buffer 再酝酿一下这个暂时 中断 的系列。等这个系列酝酿够了,再 恢复现场 ,继续还这个系列的技术债。
对于一个规模较大的企业,存在多个 Gitlab 站点是很常见的事情。
比如,我们团队在公司发布统一的 Gitlab 之前早已经搭了一个团队用的 Gitlab ,当公司开始推 Git 时,由于我们已经对自己团队的 Gitlab 做了大量的定制,因此并不打算迁移到公司的 Gitlab 。
自己搭建 Gitlab 的好处是可以随心所欲的进行定制,像加远程钩子之类的东西想加就加。但缺点就是平台的维护成本也落到了自己身上。相比之下,公司 Gitlab 则没有什么维护成本,服务的稳定性由更专业的运维人员保证,也不用考虑扩容的问题,但灵活定制就别想了。如果能够实现 Gitlab 间的数据自动同步,我们可以没有顾忌的使用自己的 Gitlab 平台,一旦出现问题,再无痛迁移到公司的 Gitlab 。这样一方面避免了单点问题,节省了维护成本;另一方面也能尽可能保证灵活可定制。本文想讨论的就是多个 Gitlab 站点间的数据同步问题。
要实现数据同步,Gitlab 官方提供了一套 备份恢复机制 。但这套机制并不能很好地满足我们的需求:
出于以上的考虑,我们自己设计了一套同步工具。与 Gitlab 官方的备份恢复机制相比,它具有以下一些优点:
下面将逐步说明整套同步的方案。为了方便描述,我把同步原 Gitlab 站点称为 A Gitlab,把同步目标站点称为 B Gitlab 。
数据的自动同步主要经历如下几步:
利用 Gitlab API 列举出 A Gitlab 中的所有 groups,然后在 B Gitlab 中自动新建不存在的组织。
列举 Gitlab 的所有组织:
GET /groups
返回示例:
[
{
"id": 1,
"name": "Foobar Group",
"path": "foo-bar",
"description": "An interesting group"
}
]
根据这个可以获取组织名(name)、组织路径(path)和组织描述(description)。
同样使用类似接口获取 B Gitlab 的所有组织。如果发现 A Gitlab 的某个组织在 B Gitlab 里不存在,可以在 B Gitlab 新增一个组织:
1
POST /groups
参数:
同步所有组织的所有仓库的代码和 wiki 文档到 B Gitlab 。
获取一个组织的所有仓库信息接口:
1
GET /groups/:id/projects
参数:
id
, name
, path
, created_at
, updated_at
或 last_activity_at
字段来排序。默认使用 created_at
。ci_enabled
字段来排序。返回示例:
[
{
"id": 4,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
"tag_list": [
"example",
"disapora client"
],
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
"path": "diaspora-client",
"path_with_namespace": "diaspora/diaspora-client",
"issues_enabled": true,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"creator_id": 3,
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
}
之后利用同个接口结合 search
参数判断 B Gitlab 上的该组织是否存在同名项目。
如果不存在该项目,可以导入该项目:
POST /projects
参数:
true
,相当于设置 visibility_level
为 20完成后 B Gitlab 即会导入 A Gitlab 中的对应仓库。
如果该项目已存在,可以利用我开源的一个 代码同步工具 来实现两个仓库之间所有分支的同步。
根据 A Gitlab ,将 B Gitlab 的已激活用户添加到组织中。并从 B Gitlab 删除 A Gitlab 中已 block 或者已移除的用户。
这里要注意的是两个站点间的用户的关联问题。我们的 Gitlab 在一开始就要求使用公司邮箱注册,而公司的 Gitlab 同样也是使用邮箱的 LDAP 账户体系,因此可以利用邮箱来关联两个站点间的账户。
获取 Gitlab 某个组织的所有用户:
GET /groups/:id/members 返回结果示例:
[
{
"id": 1,
"username": "raymond_smith",
"email": "ray@smith.org",
"name": "Raymond Smith",
"state": "active",
"created_at": "2012-10-22T14:13:35Z",
"access_level": 30
},
{
"id": 2,
"username": "john_doe",
"email": "joh@doe.org",
"name": "John Doe",
"state": "active",
"created_at": "2012-10-22T14:13:35Z",
"access_level": 30
}
]
找出两个 Gitlab 上用户的差异,并执行如下操作:
state
字段为 blocked
,则该成员可能已离职或 transfer,将该成员从 B Gitlab 中删除;添加组织成员的 API :
POST /groups/:id/members
参数:
删除组织成员的 API :
DELETE /groups/:id/members/:user_id
编辑组织成员的 API :
PUT /groups/:id/members/:user_id
另外还需要考虑 B Gitlab 不存在该用户的情况,需做容错处理。
项目的权限控制信息主要包括项目成员设定及分支保护设定。
项目成员的同步与组织成员的同步大同小异。
获取项目成员的 API :
GET /projects/:id/members
添加项目成员的 API :
POST /projects/:id/members
删除项目成员的 API :
DELETE /projects/:id/members/:user_id
编辑项目成员的 API
PUT /projects/:id/members/:user_id
分支保护同步
首先获取 A Gitlab 中一个仓库的所有分支:
GET /projects/:id/repository/branches
返回示例:
[
{
"name": "async",
"commit": {
"id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
"parents": [
{
"id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
}
],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
"message": "give Caolan credit where it's due (up top)",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2010-12-08T21:28:50+00:00",
"committed_date": "2010-12-08T21:28:50+00:00"
},
"protected": false
},
{
"name": "gh-pages",
"commit": {
"id": "101c10a60019fe870d21868835f65c25d64968fc",
"parents": [
{
"id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
}
],
"tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
"message": "Underscore.js 1.5.2",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2013-09-07T12: 58: 21+00: 00",
"committed_date": "2013-09-07T12: 58: 21+00: 00"
},
"protected": false
}
]
其中 protected
表示该分支是否受到保护。
根据分支的保护情况修改 B Gitlab 上的分支。
保护某个分支:
PUT /projects/:id/repository/branches/:branch/protect
取消某个分支的保护:
PUT /projects/:id/repository/branches/:branch/unprotect
总结
对于同时搭建了多个 Gitlab 的团队,多个 Gitlab 间的数据同步是值得去实现的事情。它一方面能避免单点问题,降低小团队的维护成本,一方面也能尽量保证小团队的定制灵活性。因此,文本列举了组织同步、仓库代码和wiki同步、组织关系同步、权限控制信息同步等四大方面的同步的方案。我将这四个类型的同步可以写成了三个工具:
group_sync
- 处理组织同步project_sync
- 处理项目代码、wiki同步member_sync
- 处理组织关系、权限控制信息的同步设定每天自动按顺序执行这几个工具的同步,完成后邮件汇报同步结果。作为实例,这是我们每天都会收到的同步结果邮件(出于保护隐私的考虑,我修改了部分隐私信息):
由于项目变动、成员变动比较频繁,当希望在计划任务之前进行某方面同步,仍然可以单独手动运行以上工具完成所需方面的同步。对于一些同步及时性要求更高的仓库,则可以通过加 post-receive 钩子调用 代码同步工具 来实现 push 后即时同步。
要注意的是,这个同步方案并没有保证 A Gitlab 的所有数据都能被完整地同步。在设计同步策略的时候,我跳过了下述类型的同步: