编辑推荐: |
本文来自hahack,文章介绍如何利用
Gitlab API 实现一套简单灵活的数据同步机制,从而实现在多个 Gitlab
站点间同步数据。 |
|
需求描述
在继续写数学系列前,我想切回去之前的 Git 系列写点东西。我想写系列文章也可以像操作系统的进程调度一样,一个系列暂时写不动了,先
保存现场 跳去另一个 topic 写点东西,同时也给自己留点 buffer 再酝酿一下这个暂时 中断
的系列。等这个系列酝酿够了,再 恢复现场 ,继续还这个系列的技术债。
对于一个规模较大的企业,存在多个 Gitlab 站点是很常见的事情。
比如,我们团队在公司发布统一的 Gitlab 之前早已经搭了一个团队用的 Gitlab ,当公司开始推
Git 时,由于我们已经对自己团队的 Gitlab 做了大量的定制,因此并不打算迁移到公司的 Gitlab
。
自己搭建 Gitlab 的好处是可以随心所欲的进行定制,像加远程钩子之类的东西想加就加。但缺点就是平台的维护成本也落到了自己身上。相比之下,公司
Gitlab 则没有什么维护成本,服务的稳定性由更专业的运维人员保证,也不用考虑扩容的问题,但灵活定制就别想了。如果能够实现
Gitlab 间的数据自动同步,我们可以没有顾忌的使用自己的 Gitlab 平台,一旦出现问题,再无痛迁移到公司的
Gitlab 。这样一方面避免了单点问题,节省了维护成本;另一方面也能尽可能保证灵活可定制。本文想讨论的就是多个
Gitlab 站点间的数据同步问题。
要实现数据同步,Gitlab 官方提供了一套备份恢复机制。但这套机制并不能很好地满足我们的需求:
需要两台机器的管理员权限。进行备份和恢复的机器都需要能 SSH 进去执行操作。而我们是不可能拥有公司
Gitlab 的管理员权限的;
会覆盖目标站点的数据。在恢复数据时,目标站点原有的一切数据都会被覆盖。而公司的 Gitlab 有很多个团队的数据,我们的同步不能影响到其他团队的数据;
Gitlab 版本兼容问题。Gitlab 的备份机制要求原站点和目标站点的 Gitlab 版本兼容,否则将恢复失败。而我们的
Gitlab 版本和公司的 Gitlab 版本并不相同,日后存在一方升级导致无法同步的可能。
出于以上的考虑,我们自己设计了一套同步工具。与 Gitlab 官方的备份恢复机制相比,它具有以下一些优点:
无需 ssh 账户权限。所有操作都通过 Gitlab API 和 Git 操作完成,不用 ssh
登录到机器进行操作;
同步数据类型灵活。可以选择同步组织、仓库代码、wiki、组织成员关系、权限控制信息等类型的数据;
不覆盖目标站点数据。只对目标站点相同组织内的数据进行同步,不影响其他团队的数据;
没有 Gitlab 版本兼容问题。同步过程利用了 Gitlab API ,而 Gitlab API
比 Gitlab 稳定,因此版本兼容问题比较少见。即使出现 API 接口的变更,也可以通过升级工具的接口调用来实现兼容。
下面将逐步说明整套同步的方案。为了方便描述,我把同步原 Gitlab 站点称为 A Gitlab,把同步目标站点称为
B Gitlab 。
数据的自动同步主要经历如下几步:
同步所有组织,如果建立了新组织,将自动给 B Gitlab 添加该组织;
同步所有组织的所有仓库的代码和 wiki 到 B Gitlab 。
同步所有用户的组织关系。
同步所有仓库的权限控制信息。
组织同步
利用 Gitlab API 列举出 A Gitlab 中的所有 groups,然后在 B Gitlab
中自动新建不存在的组织。
列举 Gitlab 的所有组织:
返回示例:
[
{ "id": 1, "name":
"Foobar Group", "path":
"foo-bar", "description":
"An interesting group"
}
] |
根据这个可以获取组织名(name)、组织路径(path)和组织描述(description)。
同样使用类似接口获取 B Gitlab 的所有组织。如果发现 A Gitlab
的某个组织在 B Gitlab 里不存在,可以在 B Gitlab 新增一个组织:
参数:
name(必须)- 组织名
path(必须)- 组织路径
description(可选)- 可选组织描述
仓库代码同步
同步所有组织的所有仓库的代码和 wiki 文档到 B Gitlab 。
获取一个组织的所有仓库信息接口:
参数:
archived (可选) - 只找出已被归档的项目
order_by (可选) - 选择基于 id, name, path, created_at, updated_at
或 last_activity_at 字段来排序。默认使用 created_at 。
sort (可选) - 选择升序还是降序排列。默认为降序。
search (可选) - 构造一个搜索条件过滤数据。
ci_enabled_first (可选) - 将结果根据是否带有 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 上的该组织是否存在同名项目。
如果不存在该项目,可以导入该项目:
参数:
name (必须) - 项目名
path (可选) - 仓库的路径。默认和项目名相同。
namespace_id (可选) - 新项目的所属的id。这里设为A Gitlab中拥有该项目的id。
description (可选) - 项目的描述。这里设为A Gitlab中该项目的描述。
issues_enabled (可选) - 是否开启 issue 。
merge_requests_enabled (可选)
builds_enabled (可选)
wiki_enabled (可选)
snippets_enabled (可选)
public (可选) - 如果为 true ,相当于设置 visibility_level 为 20
visibility_level (可选) - 项目可见度。这里设为A Gitlab中该项目的可见度。
import_url (optional) - 导入地址。这里设为A Gitlab中该项目的 http
地址。
完成后 B Gitlab 即会导入 A Gitlab 中的对应仓库。
如果该项目已存在,可以利用我开源的一个 代码同步工具 来实现两个仓库之间所有分支的同步。
用户组织关系同步
根据 A Gitlab ,将 B Gitlab 的已激活用户添加到组织中。并从 B Gitlab 删除
A Gitlab 中已 block 或者已移除的用户。
这里要注意的是两个站点间的用户的关联问题。我们的 Gitlab 在一开始就要求使用公司邮箱注册,而公司的
Gitlab 同样也是使用邮箱的 LDAP 账户体系,因此可以利用邮箱来关联两个站点间的账户。
获取 Gitlab 某个组织的所有用户:
返回结果示例:
[
{ "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 上用户的差异,并执行如下操作:
如果 B Gitlab 比 A Gitlab 多出了一些成员,将该成员删除;
如果 A Gitlab 的某个用户 state 字段为 blocked ,则该成员可能已离职或 transfer,将该成员从
B Gitlab 中删除;
如果 A Gitlab 上某个用户在 B Gitlab 上不存在,则可能是新成员,尝试添加该成员。
如果某一用户在两个 Gitlab 上的权限等级不一样,则该用户的等级可能经过修改,需要同步该权限等级到
B Gitlab 。
添加组织成员的 API :
参数:
id (必须) - 组织的 id ,也可以是路径;
user_id (必须) - 用户的 ID ,即 UM 账号
access_level (必须) - 权限等级
删除组织成员的 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 的所有数据都能被完整地同步。在设计同步策略的时候,我跳过了下述类型的同步:
用户私有仓库。这些仓库只是个人仓库,不会对组织财产造成影响。且如果要同步私有仓库,则要求两个 Gitlab
站点的账户都为管理员,因为只有管理员才能访问所有用户的私有仓库。
SSH key。用户添加的所有 SSH key 无法同步。
头像。组织、用户、仓库的头像未做同步。
issue。由于我们的 Gitlab 并不用来进行 bug 跟踪管理,所以我跳过了这方面的同步。读者也可以利用
Gitlab API 实现 issue 的同步。
附件。Wiki 中的附件是独立于仓库之外的,需要单独备份。例如使用 rsync 。 |