當go get遇上gitlab

yhf_szb發表於2017-11-30

前言

go get命令可以說是 golang 開發者最常用的命令了,通過它我們可以輕鬆獲得各種開源倉庫中的包,並且比較方便的在不同的開發機快速部署開發環境。

> 此處應有版本依賴的問題,但聽說新版的 go 會處理。

但作為企業行為,不是所有的程式碼包都適合放在公開的網站上,而開源的又適用於中小型企業的自建 git 倉庫工具中,gitlab 無疑是耀眼的一個,如果配合 docker,一鍵部署簡直不要太舒服。

自建倉庫的go get

其實 golang 在設計的時候是可以支援go get獲取自建倉庫的,詳細原理網上很多,不羅嗦,簡單講,當執行go get your-web.com/your-project的時候,go 其實會提交一個HTTP GET 到網址https://you-web.com/your-project?go-get=1,此時如果這個網址能在headmeta標籤中返回以下格式的內容時,就可以告訴go客戶端應該再到哪裡獲取倉庫。

> 注意,預設情況下 go 會且僅會從 https 的網址獲取資料!

<html>
    <head>
        <meta content="szyhf/go-dicache git https://github.com/szyhf/go-dicache" name="go-import">
    </head>
</html>

其中meta標籤中的name是必填項,內容必須是go-import,而contat的格式為匯入路徑 VCS型別 倉庫路徑,例如,上述程式碼的含義就是從https://github.com/szyhf/go-dicache下載git倉庫並放到匯入路徑為szyhf/go-dicache的 $GOPATH 中。

> 至於域名怎麼能訪問,怎麼輸出這個 meta,相信對於各位童鞋來說肯定是不是什麼 problem,跳過

更多說明可以看這裡go get 命令

遇上 gitlab

那麼對於自建倉庫的 gitlab,應該怎麼實現這個功能呢?其實 gitlab 很早就支援了 go get,例如你的 gitlab 網站部署在gitlab.hello.com,你要 get 的專案是gitlab.hello.com/foo/bar,那麼直接執行go get gitlab.hello.com/foo/bar就可以了,gitlab 會自動在返回的網頁中設定合適的meta標籤的。

但實際使用的時候,我們知道,很多時候我們之所以用自建的gitlab,是因為這個倉庫見不得光,說白了,我們自己git clone的時候還需要輸入一下密碼,go get顯然也繞不過這個問題。

而預設情況下,gitlab返回的 meta 標籤中的 url 是https型別的,而實際上更多時候,我們都是通過ssh的方式實現獲取倉庫,因此,我們需要對 gitlab 做一定的改造。

當前筆者使用的gitlab版本是9.3.6,對go get的支援是用過 ruby-rails 中的 Middleware 的方式實現的,很傳統,如果懂 ruby 的話可以試試直接改,檔案是gitlab/embedded/service/gitlab-rails/lib/gitlab/middleware/go.rb,此處不多說。

> 主要考慮要改原始碼不是很優雅,特別是要處理 gitlab 的升級的時候。

此處給一個不需要懂 ruby 的非侵入式方案,因為公司的 gitlab 是搭配 nginx 使用的,所以在處理對 gitlab 的請求的時候,加入以下配置,可以達到一樣的效果:

if ($http_user_agent ~* "go") {
    return 200 "<!DOCTYPE html><head><meta content='$host$uri git ssh://git@$host:$uri.git' name='go-import'></head></html>";
}

簡單解釋一下,來自go get的 HTTP 請求中,User Agent都是以go作為開頭的,而且go也不會跟現在任何主流瀏覽器衝突,所以當發現$http_user_agentgo開頭的時候,直接返回一個固定的字串,字串中注意倉庫路徑的拼接要加上ssh://,要不go1.8以下的版本無法識別。

> 上述是我第一次的方案,go get gitlab.hello.com/foo/bar成功,順利按照預期工作。

然後工作了一陣子之後忽然又出現了新的問題,subpackage

原因很簡單,當我們在go get某個專案時,如果這個專案依賴於gitlab.hello.com/foo/bar/you/hu包,那麼go get實際提交的請求會變成https://gitlab.hello.com/foo/bar/you/hu,而實際上並不存在這個倉庫,如果按方案 1 的實現邏輯,會嘗試下載git@gitlab.hello.com/foo/bar/you/hu.git

很遺憾,這個倉庫並不存在,真正存在的是gitlab.hello.com/foo/bar.git,那麼應該怎麼處理呢?結合nginx的正規表示式重定位的功能,更新的配置如下:

location ~* ^/[^/]+/[^/]+$ {
    if ($http_user_agent ~* '^go.*') {
        return 200 "<!DOCTYPE html><head><meta content='$host$uri git ssh://git@$host:$uri.git' name='go-import'></head></html>";
    }
    proxy_cache off;
    proxy_pass http://gitlab-workhorse;
}
location ~* ^/(?<holder>[^/]+)/(?<project>[^/]+)/.*$ {
    set $goRedirect 'https://$host/$holder/$project?$args';
    if ($http_user_agent ~* '^go.*') {
        return 301 $goRedirect;
    }
    proxy_cache off;
    proxy_pass http://gitlab-workhorse;
}

其中proxy_cache off;proxy_pass http://gitlab-workhorse;是 gitlab 官方文件中給出的設定。

> 其實很容易理解。

主要來解釋一下兩個location,首先:

~*表示開始不區分大小寫地匹配後邊給出的正則。

正則^/[^/]+/[^/]+$是為了匹配形如/foo/bar的路徑結構,如果匹配成功,繼續檢查User-Agent,如果符合go,則按第一個方案返回結果,如果不符合,則按一般的gitlab請求進行處理。

正則^/(?<holder>[^/]+)/(?<project>[^/]+)/.*$是為了匹配形如/foo/bar/you/hu/hu的結構,其中的小括號表示對其第一二個斜槓之間的字串進行捕捉,並賦值給變數$holder$project,然後判定User-Agent,如果符合go,則將請求重定位給/foo/bar,也就會再交給第一個正則處理,最後獲得一致的結果。

顯然第二個方案比第一個方案複雜了不少,但也都是很標準的nginx配置邏輯,未必優雅,但還是很實用的。

> 一般來說小型企業的程式碼庫並不會有很高的訪問頻率,哪怕 proxy 稍微慢一點,影響也不大。

Docker

如果使用 docker 版並使用了 docker-compose,配置檔案中的選項可以參考如下:

environment:
    GITLAB_OMNIBUS_CONFIG: |
        nginx['custom_gitlab_server_config'] = "location ~* ^/[^/]+/[^/]+$$ {\n    if ($$http_user_agent ~* '^go.*') {\n        return 200 \"<!DOCTYPE html><html><head><meta content='$$host$$uri git ssh://git@$$host:$$uri.git' name='go-import'></head></html>\";\n    }\n  proxy_cache off;\n    proxy_pass http://gitlab-workhorse;\n}\nlocation ~* ^/(?<holder>[^/]+)/(?<project>[^/]+)/.*$$ {\n    set $$goRedirect 'https://$$host/$$holder/$$project?$$args';\n    if ($$http_user_agent ~* '^go.*') {\n        return 301 $$goRedirect;\n  }\n  proxy_cache off;\n    proxy_pass http://gitlab-workhorse;\n}"

> 使用\$\$ 可以防止引數被當成環境變數使用。

小結

代理的思維方式可以解決很多問題。

公眾號廣告=。=

原文連結 公眾號二維碼

更多原創文章乾貨分享,請關注公眾號
  • 當go get遇上gitlab
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章