我們一起聊聊 Go 模塊使用 GitLab subgroups 的問題
大家好,我是煎魚。
最近幫忙小伙伴處理了一個小問題,感覺五六年前就有人問過我,當(dāng)年覺得沒啥大問題記錄。沒想到。。。2024 年了,還是有同學(xué)表示他的姿勢搜不到相關(guān)的解決辦法。
今天主打一個分享和記載,看看有沒有也踩過坑的朋友。(結(jié)果我發(fā)文前兩天就有社區(qū)的朋友問到了我??)
我感覺 2027 年這問題都不會解決。
功能介紹
在 GitLab 中,提供了一種叫做子組(subgroups)的功能特性。
它允許對項目倉庫進(jìn)行進(jìn)一步的分組,而無需創(chuàng)建多個組織去實現(xiàn)不同內(nèi)容的存放。
創(chuàng)建子組的截圖和地方:
新建子組
在項目列表下子組的展示:
列表展示
注:這個功能,我翻了下 GitHub 是沒有的。并且有人在 Community Discussions#4837 提出也想要,但官方?jīng)]有人回應(yīng)此項提議。
問題背景
雖然這個功能有一定的特色。但是子組(subgroups)和 Go 模塊管理有一定的水土不服。我們直接使用 go get 命令試圖拉取子組時,就會出現(xiàn)問題。
在最常用的 group/project 格式下,模擬 go get 命令直接拉?。?/p>
curl -X GET "https://gitlab.xxx.com/libraries/example?go-get=1"
<html><head><meta name="go-import" cnotallow="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" cnotallow="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。獲取到的層級都錯了!
不支持的原因
現(xiàn)階段來看,根本原因是 Go 的 go get 實現(xiàn)和 GitLab 的原因共同導(dǎo)致。互相不完全適配。Go 覺得 GitLab 這個太定制化,GitLab 覺得是 Go 實現(xiàn)不完整。
Go 為了使用 SSH 身份驗證,go get 需要知道項目位于什么地方,然后才能去獲取他。
以下是梳理的流程和步驟:
1、對于 group/subgroup/project 中的項目,go get 將發(fā)送以下請求:
- 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、這些請求未經(jīng)授權(quán),因此 GitLab 無法提供指向項目的正確鏈接 (也就是預(yù)期的結(jié)果:https://gitlab.com/group/subgroup/project.git)。
3、相反,GitLab 會返回 https://gitlab.com/group/subgroup.git 作為響應(yīng)結(jié)果。(如此返回的目的是:防止未經(jīng)身份驗證的用戶訪問,避免出現(xiàn)暴露項目存在的安全風(fēng)險)
4、然后 Go 會嘗試使用 SSH 身份驗證 ssh://gitlab.com/group/subgroup.git 來 git clone 倉庫。想也知道,拉取的結(jié)果失敗了,因為這個倉庫并不存在。
因此對于 GitLab 子組中的 Go 私有項目,用戶必須通過 HTTPS 認(rèn)證,這樣 Go 的 go get 命令才能通過 GitLab 的訪問校驗,再拉取到正確的響應(yīng)結(jié)果。
解決方案
現(xiàn)階段的解決方案大致有兩種,都是一些繞過校驗或邏輯的操作。
使用 .netrc 鑒權(quán)
GitLab 文檔 Authenticate Go requests to private projects[1] 和 Go 文檔 Passing credentials to private proxies[2],推薦的是通過定義 .netrc 文件來實現(xiàn)該鑒權(quán)訪問。
但有前置條件的要求:
- GitLab 實例必須可通過 HTTPS 訪問。
- 用到的賬號必須擁有 read_api 權(quán)限的個人訪問令牌。
根據(jù)你的 OS 環(huán)境不同,創(chuàng)建一個 .netrc 文件,并填入相關(guān)信息:
machine gitlab.example.com
login <gitlab_user_name>
password <personal_access_token>
需要注意:在 Windows 上,Go 讀取 ~/_netrc 而不是 Linux 的 ~/.netrc 文件。
或者可以直接在 GOPROXY URL 鑒權(quán)憑證:
GOPROXY=https://jrgopher:hunter2@proxy.corp.example.com
采用此方法時請務(wù)必小心:環(huán)境變量可能會出現(xiàn)在 shell 歷史記錄和日志中。(不推薦使用)
但總的來說,netrc 這個方案,有部分社區(qū)反饋不是很認(rèn)可,因為有安全隱患,存在一定的風(fēng)險。還要配這配那的。
使用 replace .git 繞過邏輯
有一種方法更簡單的方法,可以跳過 go get 請求并強(qiáng)制 Go 直接使用 Git 身份驗證,但需要修改模塊名稱。
直接修改 go.mod 文件,采用 replace 的方式給實際引用的模塊名的項目名增加 .git。當(dāng)然,如果你不想 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 標(biāo)識符符(例如:.bzr、.fossil、.git、.hg、.svn)。go get 命令就會使用該路徑限定符之前的所有內(nèi)容作為倉庫 URL。
例如,對于 example.com/foo.git/bar 模塊,go 命令會使用 git 下載 example.com/foo.git 的版本庫,并在 bar 子目錄中找到該模塊。
總結(jié)和吐槽
比較可惜的是,這個問題,至少 7 年前(2017 年)就有人提出了,可惜到現(xiàn)在 2024 年,都沒有解決。
追根溯源上,Go 團(tuán)隊并不想完整實現(xiàn)一套完整的單獨(dú)的程序去支持這個。Go 團(tuán)隊負(fù)責(zé)人 rsc(Go 模塊他獨(dú)立開發(fā)、應(yīng)用和推廣的)只想做一個符合標(biāo)準(zhǔn)的,因此 Go 最終支持了讀取 $HOME/.netrc。后續(xù)至此再無更多的動作。社區(qū)反饋也不會說特別激烈。
而對于 GitLab 而已,他是乙方。客戶各種吐槽。因此他們最有動力去推進(jìn)解決這個事情。
近期由于 Go 團(tuán)隊多年對此的冷漠。GitLab 開始也有提出 Allow to set a go-modules folder for private Go projects[3]。
希望允許用戶在 group 或者子組為私有 Go 模塊設(shè)置 go-modules 的專屬標(biāo)識再通過第二點(diǎn)解決方案的 .git 的方式繞過邏輯:
預(yù)想的定制
不過這個還在討論中,反對意見也不小。暫且茍住。
大家在實際使用上,可以先看看 .netrc 或 replace .git 的方式??赡芮罢吒鼧?biāo)準(zhǔn),后者更易用。這樣團(tuán)隊成員就不用每次都配置一遍了。
參考資料
[1]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