當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}"

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

小結

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

公眾號廣告=。=

原文連結 公眾號二維碼

相關文章