go-gitlab包源码探寻与心得
gitlab 基本上是每个公司内部代码仓库的首选,那么与之进行一些交互就是一个常见的需求了,在写了一些脚本与之交互之后,越发感受到此项目设计的成熟,因此特来记录一下相关文档。
# 1,物料准备
- gitlab 环境,怎么部署这里就不详述了,可参考:Gitlab 简单部署 (opens new window)最好是 12.x 版本之后的版本,有一些接口在 11 版本中还不支持,比如项目移动分支,获取 token 的接口。
- gitlab 官方文档 (opens new window)
- gitlab-api 接口文档 (opens new window)
- gitlab-api 中文接口文档 (opens new window)
- go-gitlab 包 (opens new window)
- go-gitlab 包接口说明文档 (opens new window)
# 2,初始化连接
一般情况下,我们最好直接在管理员账号中创建一个 access_token,初始化中的认证工作都基于这个 token 来进行。
var (
git *gitlab.Client
token string = "ceg46SwaL7yy"
url string = "https://gitlab.test.com/api/v4"
)
func init() {
var err error
git, err = gitlab.NewClient(token, gitlab.WithBaseURL(url))
if err != nil {
fmt.Printf("initclienterr:%v\n", err)
panic(err)
} else {
fmt.Println("初始化完成")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意我们在开头声明一个 *gitlab.Client
类型的变量,初始化的时候直接将这个客户端对象赋值给这个变量,从而在全局都可以通过此变量直接与 gitlab 进行交互。
在源码当中,我们能看到这个对象拥有的属性如下:
// A Client manages communication with the GitLab API.
type Client struct {
// HTTP client used to communicate with the API.
client *retryablehttp.Client
// Base URL for API requests. Defaults to the public GitLab API, but can be
// set to a domain endpoint to use with a self hosted GitLab server. baseURL
// should always be specified with a trailing slash.
baseURL *url.URL
//== 中间部分省略 ==//
Projects *ProjectsService
Releases *ReleasesService
Users *UsersService
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这些对象都是此包给我们提供的能力,我们可以操作这些对象附带的属性,从而实现我们的需求,接下来我将通过项目这个对象来作为例子进行讲解,从而深入地认识并理解这个包的用法。
# 3,项目的交互
# 1,熟悉结构体
go-gitlab 的包采用了非常优雅的面向对象编程思想,当然这其实也基源于 gitlab 官方接口文档设计的优秀。在进行一些项目相关的操作时,该包会将接口对应的返回值赋给项目这个对象,那么这个对象所拥有的一些方法就都可以直接使用了。
这里针对这一个示例稍微详细点讲解一下,通过编辑器代码追踪我们可以看到项目这个结构体的定义:
// Project represents a GitLab project.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html
type Project struct {
ID int `json:"id"`
Description string `json:"description"`
DefaultBranch string `json:"default_branch"`
Public bool `json:"public"`
Visibility VisibilityValue `json:"visibility"`
SSHURLToRepo string `json:"ssh_url_to_repo"`
HTTPURLToRepo string `json:"http_url_to_repo"`
//== 中间部分省略 ==//
BuildCoverageRegex string `json:"build_coverage_regex"`
IssuesTemplate string `json:"issues_template"`
MergeRequestsTemplate string `json:"merge_requests_template"`
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go-gitlab 包非常优秀的一点在于,每一个结构体或者接口,作者都将对应的官方文档地址标注在了注释当中,从而便于我们能够直接查阅官方文档。
我们可以看到Project (opens new window)这个结构体拥有众多属性,当我们通过接口获取或者操作的时候,都可以借助于这些属性,进行非常方便的操作。
# 2,查项目
查询接口是最常见最常用的,我们就先来看看这个接口怎么使用,通常,我们可能并不知道某个接口对应的方法名字叫什么,这个时候要么是去官方接口说明文档查看,要么是凭借经验,在编辑器里善用补全来进行。
当我们了解了 go-gitlab 这种面向对象的编程思路之后,就可以拿着开头初始化的 client 对象,来操作它里边的内容,这个时候可以输入一个git.Project.
就能看到与项目相关的所有方法了:
通常,查询接口的函数命名无非就是这么几个关键字:List
,Find
,Select
,Cat
….于是如果一开始没什么头绪的话,就可以通过关键字来进行模糊补全,这里我们往下翻可以看到 go-gitlab 使用的是 List 关键字:
git.Projects.ListProjects()
这个时候可以点击方法跳转到源码当中,看到如下内容:
// ListProjects gets a list of projects accessible by the authenticated user.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/projects.html#list-projects
func (s *ProjectsService) ListProjects(opt *ListProjectsOptions, options ...RequestOptionFunc) ([]*Project, *Response, error) {
req, err := s.client.NewRequest(http.MethodGet, "projects", opt, options)
if err != nil {
return nil, nil, err
}
var p []*Project
resp, err := s.client.Do(req, &p)
if err != nil {
return nil, resp, err
}
return p, resp, err
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
首先还是官方接口文档的地址,以及接口的具体定义,函数定义中的三段内容简单说明如下:
(s *ProjectsService)
:表示ProjectsService
这个对象实例。ListProjects()
:表示ProjectsService
这个对象拥有的ListProjects()
方法。其中的两个参数都是在调用此方法时的一些附加属性。opt *ListProjectsOptions
:查询时的一些参数,通常我们会用到里边的ListOptions
参数。options ...RequestOptionFunc
:自定义请求参数,一般情况下,这个参数都保持默认。
([]*Project, *Response, error)
:返回值有三个,一个指针类型的项目切片,一个状态码,一个错误。
了解了如上内容之后,我们可以简单定义如下代码:
// GetAllProject 获取所有项目
func GetAllProject() ([]*gitlab.Project, error) {
lbo := &gitlab.ListProjectsOptions{ListOptions: gitlab.ListOptions{Page: 1, PerPage: 50}}
var pro []*gitlab.Project
for {
p, _, err := git.Projects.ListProjects(lbo)
if err != nil {
fmt.Printf("list projects failed:%v\n", err)
return nil, err
}
for _, v := range p {
pro = append(pro, v)
}
if len(p) < 50 {
break
}
lbo.ListOptions.Page++
}
return pro, nil
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
通过官方 client 包,我们可以做一个获取全部项目的方法,该方法不接收参数,然后返回所有项目的切片。
这里需要注意的是查询的参数,这种用法是一种比较常见的查询接口的用法,一般接口都不会直接将所有数据返回,而会设计出分页的装置,gitlab 亦是如此,一开始我在使用 git.Projects.ListProjects()
方法想要获取所有项目时,发现总是只拿到了固定的五十个项目信息,就是因为这个方法默认也是使用了分页的机制。
当我们用如上方法拿到所有项目之后,一般情况可以使用遍历的方法将项目遍历出来,然后利用项目的结构体对象,来获取我们关心的信息。
# 3,创建项目
其实有了如上的思路之后,一般情况下,我们就可以同样借助于编辑器自动补全对应的功能。首先我们可以看到创建方法的定义:
func (s *ProjectsService) CreateProject(opt *CreateProjectOptions, options ...RequestOptionFunc) (*Project, *Response, error) {
核心在于 opt *CreateProjectOptions
,再往深处追踪,我们可以看到这个参数项,就是对应着上边 get 出来的项目的一个个属性,现在创建项目也是一样,可以通过定义这些属性,来创建一个符合预期的项目:
func CreateProject(group, name, desc string) {
gid, err := GetGroupID(group)
if err != nil {
fmt.Printf("get group id err:%v\n", err)
return
}
p := &gitlab.CreateProjectOptions{
Name: gitlab.String(name),
NamespaceID: gitlab.Int(gid),
Description: gitlab.String(desc),
MergeRequestsEnabled: gitlab.Bool(true),
JobsEnabled: gitlab.Bool(true),
WikiEnabled: gitlab.Bool(true),
SnippetsEnabled: gitlab.Bool(true),
Visibility: gitlab.Visibility(gitlab.PrivateVisibility),
}
project, _, err := git.Projects.CreateProject(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(project.Name)
fmt.Println(project.WebURL)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这里只是一个示例代码,并不代表能够直接运行,如果你是封装在自己的平台,或者仅仅是做一个脚本,那么可能还需要添加一些前置检测的方法来辅助,比如:
- 先判断传递的分组是否存在。
- 然后判断此项目是否已存在。
- 以及其他的一些你想要注入的参数。
好了,关于与项目的交互就说到这里,其他的需求,只要经过合理的涉及,参考官方 api 以及 client 包的方法,都能够很方便高效地开发出来。
# 4,感受
事实上我大概是为了想要写一些感受,才写了这篇文章的,什么感受呢,那就是一个项目如果有好的成熟的设计,会时时处处造福后来人!
真正接触了解过 gitlab-api 的同学肯定能体会到,此项目 api 设计的统一性以及优雅度,让我们无论是通过 curl 命令行与之交互,还是基于一些客户端包的交互,都感到优雅与丝滑。
但其实能够设计出如此成熟统一的架构,是非常困难的,可以想见 gitlab 项目的开发者一定基于很多实际开发经验,下了很多功夫对之进行设计与实现,从这个角度来说,应该致敬。
我想,让人感到丝滑与优雅的原因,大概是合理,统一的接口规范,是的,就是规范,当一个项目,有了统一的接口入参规范,统一的返回规范,对于使用者而言,就是一种优雅的感受。有人可能会说规范应该是一个项目要求的基础,的确是基础,但是,我们实际生产中开发维护的项目,真的能有多少是站在统一的规范之上运行的呢,就我目前接触维护的而言,实际操作起来其实真正能够在公司订立统一的规范,每个开发者又能对齐认识,并在开发中能够严格遵守的,实在少之又少。
很多应该在基本上就做好的,其实早都丢得一干二净,很多应该严格遵照的红线,其实无形中就在跨越。这些都是一个项目,一个团队应该时常拿出来审视自省的。