Go 模块使用 GitLab subgroups 的问题
共 4990字,需浏览 10分钟
·
2024-07-03 17:41
大家好,我是煎鱼。
最近帮忙小伙伴处理了一个小问题,感觉五六年前就有人问过我,当年觉得没啥大问题记录。没想到。。。2024 年了,还是有同学表示他的姿势搜不到相关的解决办法。
今天主打一个分享和记载,看看有没有也踩过坑的朋友。(结果我发文前两天就有社区的朋友问到了我😅)
我感觉 2027 年这问题都不会解决。
功能介绍
在 GitLab 中,提供了一种叫做子组(subgroups)的功能特性。
它允许对项目仓库进行进一步的分组,而无需创建多个组织去实现不同内容的存放。
创建子组的截图和地方:
在项目列表下子组的展示:
注:这个功能,我翻了下 GitHub 是没有的。并且有人在 Community Discussions#4837 提出也想要,但官方没有人回应此项提议。
问题背景
虽然这个功能有一定的特色。但是子组(subgroups)和 Go 模块管理有一定的水土不服。我们直接使用 go get
命令试图拉取子组时,就会出现问题。
在最常用的 group/project
格式下,模拟 go get
命令直接拉取:
curl -X GET "https://gitlab.xxx.com/libraries/example?go-get=1"
<html><head><meta name="go-import" content="gitlab.xxx.com/libraries/example git https://gitlab.xxx.com/libraries/example.git" /></head></html>
可以看到是正常返回:gitlab.xxx.com/libraries/example.git
。
那如果是子组的模式呢?
我们按照 group/subgroup/project
再获取试试:
curl -X GET "https://gitlab.xxx.com/libraries/subgroup1/example?go-get=1"
<html><head><meta name="go-import" content="gitlab.xxx.com/libraries/subgroup1 git https://gitlab.xxx.com/libraries/subgroup1.git" /></head></html>
可以看到返回的是:gitlab.xxx.com/libraries/subgroup1.git
,这显然不正确。我们要获取的是 gitlab.xxx.com/libraries/subgroup1/example
。获取到的层级都错了!
不支持的原因
现阶段来看,根本原因是 Go 的 go get
实现和 GitLab 的原因共同导致。互相不完全适配。Go 觉得 GitLab 这个太定制化,GitLab 觉得是 Go 实现不完整。
Go 为了使用 SSH 身份验证,go get
需要知道项目位于什么地方,然后才能去获取他。
以下是梳理的流程和步骤:
1、对于 group/subgroup/project
中的项目,go get
将发送以下请求:
-
GET https://gitlab.com/group/subgroup/project?go-get=1
-
GET https://gitlab.com/group/subgroup?go-get=1
-
GET https://gitlab.com/group?go-get=1
2、这些请求未经授权,因此 GitLab 无法提供指向项目的正确链接 (也就是预期的结果:https://gitlab.com/group/subgroup/project.git
)。
3、相反,GitLab 会返回 https://gitlab.com/group/subgroup.git
作为响应结果。(如此返回的目的是:防止未经身份验证的用户访问,避免出现暴露项目存在的安全风险)
4、然后 Go 会尝试使用 SSH 身份验证 ssh://gitlab.com/group/subgroup.git
来 git clone
仓库。想也知道,拉取的结果失败了,因为这个仓库并不存在。
因此对于 GitLab 子组中的 Go 私有项目,用户必须通过 HTTPS 认证,这样 Go 的 go get
命令才能通过 GitLab 的访问校验,再拉取到正确的响应结果。
解决方案
现阶段的解决方案大致有两种,都是一些绕过校验或逻辑的操作。
使用 .netrc 鉴权
GitLab 文档 Authenticate Go requests to private projects[1] 和 Go 文档 Passing credentials to private proxies[2],推荐的是通过定义 .netrc
文件来实现该鉴权访问。
但有前置条件的要求:
-
GitLab 实例必须可通过 HTTPS 访问。 -
用到的账号必须拥有 read_api 权限的个人访问令牌。
根据你的 OS 环境不同,创建一个 .netrc
文件,并填入相关信息:
machine gitlab.example.com
login <gitlab_user_name>
password <personal_access_token>
需要注意:在 Windows 上,Go 读取 ~/_netrc
而不是 Linux 的 ~/.netrc
文件。
或者可以直接在 GOPROXY URL 鉴权凭证:
GOPROXY=https://jrgopher:hunter2@proxy.corp.example.com
采用此方法时请务必小心:环境变量可能会出现在 shell 历史记录和日志中。(不推荐使用)
但总的来说,netrc 这个方案,有部分社区反馈不是很认可,因为有安全隐患,存在一定的风险。还要配这配那的。
使用 replace .git 绕过逻辑
有一种方法更简单的方法,可以跳过 go get
请求并强制 Go 直接使用 Git 身份验证,但需要修改模块名称。
直接修改 go.mod 文件,采用 replace 的方式给实际引用的模块名的项目名增加 .git
。当然,如果你不想 replace,直接引用模块后缀是 .git 也是可以的。
如下所示:
require(
gitlab.xxx.com/group/subgroup/project v1.7.0
)
replace(
gitlab.xxx.com/group/subgroup/project => gitlab.xxx.com/group/subgroup/project.git v1.7.0
)
Go 模块能用这个骚操作的原因是:如果模块路径的某个组件末尾有一个 VCS 标识符符(例如:.bzr
、.fossil
、.git
、.hg
、.svn
)。go get
命令就会使用该路径限定符之前的所有内容作为仓库 URL。
例如,对于 example.com/foo.git/bar
模块,go
命令会使用 git
下载 example.com/foo.git
的版本库,并在 bar
子目录中找到该模块。
总结和吐槽
比较可惜的是,这个问题,至少 7 年前(2017 年)就有人提出了,可惜到现在 2024 年,都没有解决。
追根溯源上,Go 团队并不想完整实现一套完整的单独的程序去支持这个。Go 团队负责人 rsc(Go 模块他独立开发、应用和推广的)只想做一个符合标准的,因此 Go 最终支持了读取 $HOME/.netrc
。后续至此再无更多的动作。社区反馈也不会说特别激烈。
而对于 GitLab 而已,他是乙方。客户各种吐槽。因此他们最有动力去推进解决这个事情。
近期由于 Go 团队多年对此的冷漠。GitLab 开始也有提出 Allow to set a go-modules folder for private Go projects[3]。
希望允许用户在 group 或者子组为私有 Go 模块设置 go-modules 的专属标识再通过第二点解决方案的 .git
的方式绕过逻辑:
不过这个还在讨论中,反对意见也不小。暂且苟住。
大家在实际使用上,可以先看看 .netrc
或 replace .git
的方式。可能前者更标准,后者更易用。这样团队成员就不用每次都配置一遍了。
推荐阅读
Authenticate Go requests to private projects: https://docs.gitlab.com/ee/user/project/use_project_as_go_package.html#authenticate-go-requests-to-private-projects
[2]Passing credentials to private proxies: https://go.dev/ref/mod#private-module-proxy-auth
[3]Allow to set a go-modules folder for private Go projects: https://gitlab.com/gitlab-org/gitlab/-/issues/437005
关注和加煎鱼微信,
一手消息和知识,拉你进技术交流群👇
你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路。
日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!
原创不易 点赞支持