背景
使用docker search mysql
這個命令可以顯示有哪些映象,但不能顯示有哪些tag
,可是我們使用docker pull mysql:TAG
下載映象的時候卻必須要指定標籤,而且很多時候我們要指定特定的版本,標籤只能從docker hub上面找:
https://hub.docker.com/_/mysql?tab=tags
這樣太累了,我們來搞個專案直接在命令列中查詢指定映象的標籤。
示例
最終效果見:
https://github.com/safeie/docker-search-tag
docker-search-tag mysql
顯示結果:
Tags for mysql:
TAG SIZE(MB) LASTPUSH
latest 144 2021-12-02T11:43:26Z
8.0.4-rc 83 2018-03-14T08:42:43Z
8.0.4 83 2018-03-14T08:42:43Z
8.0.3 107 2018-02-17T09:43:00Z
8.0.1 86 2017-06-24T13:36:11Z
8.0.0 126 2017-04-10T23:29:13Z
8.0 144 2021-12-02T11:43:21Z
8 144 2021-12-02T11:43:20Z
5.7.36 147 2021-12-02T11:43:09Z
5.7.35 147 2021-10-12T16:42:35Z
5.6.28 106 2016-02-02T22:41:46Z
5.6.27 106 2015-12-17T03:17:56Z
5.6 98 2021-12-02T11:43:04Z
5.5 63 2019-05-10T23:43:32Z
5 147 2021-12-02T11:42:49Z
基本思路就是,hub.docker.com這裡的查詢頁面有提供JSON的結果,我們可以通過分析JSON資料,列印出想要的結果。
編碼
構造命令列引數
我們構造的命令列引數希望是這樣的:
Usage: docker-search-tag NAME [NUM]
支援兩個引數:
- NAME 映象名稱
- NUM 標籤數量,可選引數
func main() {
var name, num string
if len(os.Args) < 2 || len(os.Args[1]) == 0 {
fmt.Println("Usage: docker-search-tag NAME [NUM]")
return
}
name = os.Args[1]
num = "100"
if len(os.Args) == 3 && len(os.Args[2]) > 0 {
num = os.Args[2]
}
fmt.Println(name, num)
}
通過 os.Args
來判斷輸入的引數,第零個引數是程式本身,第一個為 NAME
,第二個為 NUM
,第二個引數可省略,如果沒有輸入引數,那麼給出提示並終止程式。
請求HTTP資料
構造URL並請求資料
func request(name, num string) {
url := fmt.Sprintf("https://registry.hub.docker.com/v2/repositories/library/%s/tags/?page_size=%s", name, num)
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Get information from registry.hub.docker.com err: %v\n", err)
return
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("Read data err: %v\n", err)
return
}
fmt.Printf("data json:\n%s\n", respBody)
}
解析JSON資料
Go裡面解析資料不像PHP/Python那樣簡單,需要先定義結構,直接將json資料解析到結構體。
docker hub返回的結構簡化後是這個樣子:
{
"count": 124,
"next": "https://registry.hub.docker.com/v2/repositories/library/mysql/tags/?page=2\u0026page_size=100",
"previous": null,
"results": [
{
"creator": 7,
"id": 20021,
"image_id": null,
"images": [
{
"architecture": "amd64",
"features": "",
"variant": null,
"digest": "sha256:eb791004631abe3bf842b3597043318d19a91e8c86adca55a5f6d4d7b409f2ac",
"os": "linux",
"os_features": "",
"os_version": null,
"size": 151446665,
"status": "active",
"last_pulled": "2021-12-15T03:46:31.940386Z",
"last_pushed": "2021-12-02T11:43:19.029296Z"
}
],
"last_updated": "2021-12-02T11:43:26.106168Z",
"last_updater": 1156886,
"last_updater_username": "doijanky",
"name": "latest",
"repository": 21179,
"full_size": 151446665,
"v2": true,
"tag_status": "active",
"tag_last_pulled": "2021-12-15T03:46:31.940386Z",
"tag_last_pushed": "2021-12-02T11:43:26.106168Z"
},
{
"creator": 1156886,
"id": 172676269,
"image_id": null,
"images": [
{
"architecture": "amd64",
"features": "",
"variant": null,
"digest": "sha256:eb791004631abe3bf842b3597043318d19a91e8c86adca55a5f6d4d7b409f2ac",
"os": "linux",
"os_features": "",
"os_version": null,
"size": 151446665,
"status": "active",
"last_pulled": "2021-12-15T03:46:31.940386Z",
"last_pushed": "2021-12-02T11:43:19.029296Z"
}
],
"last_updated": "2021-12-02T11:43:23.591673Z",
"last_updater": 1156886,
"last_updater_username": "doijanky",
"name": "8.0.27",
"repository": 21179,
"full_size": 151446665,
"v2": true,
"tag_status": "active",
"tag_last_pulled": "2021-12-15T03:46:31.940386Z",
"tag_last_pushed": "2021-12-02T11:43:23.591673Z"
}
]
}
我們只關注特定的欄位,返回的tag數量,請求的url和tag列表,具體到tag結構中,我們關心tag名稱,映象大小,更新時間等,對應的結構定義如下:
type response struct {
Count int `json:"count"`
Next string `json:"next"`
Results []responseResult `json:"results"`
}
type responseResult struct {
Name string `json:"name"`
FullSize int `json:"full_size"`
V2 bool `json:"v2"`
TagStatus string `json:"tag_status"`
TagLastPulled time.Time `json:"tag_last_pulled"`
TagLastPushed time.Time `json:"tag_last_pushed"`
}
定義好結構,就可以解析結構列印我們要的資料了:
func parse(name string, respBody []byte) {
respData := new(response)
err := json.Unmarshal(respBody, &respData)
if err != nil {
fmt.Printf("json.Unmarshal data err: %v\n", err)
return
}
if len(respData.Results) == 0 {
fmt.Printf("there is no tag data for name: %s\n", name)
return
}
fmt.Printf("Tags for %s:\n\n", name)
fmt.Printf("%s %s %s\n", "TAG", "SIZE(MB)", "LASTPUSH")
for _, v := range respData.Results {
fmt.Printf("%s %10d %s\n",
v.Name,
v.FullSize/1024/1024,
v.TagLastPushed.Format(time.RFC3339),
)
}
}
整體結構就完成了,然後在 main
函式最後呼叫 request(name, num)
,在 request
函式最後呼叫 parse(name, respBody)
就可以除錯了。
需要先初始化 go mod 不然無法編譯go mod init github.com/safeie/docker-search-tag
go mod tidy
到這裡我們就可以列印出需要的tag資訊了,我們編譯測試一下:
go build
./docker-search-tag mysql
結果應該是這樣的:
Tags for mysql:
TAG SIZE(MB) LASTPUSH
latest 144 2021-12-02T11:43:26Z
8.0.27 144 2021-12-02T11:43:23Z
8.0 144 2021-12-02T11:43:21Z
8 144 2021-12-02T11:43:20Z
5.7.36 147 2021-12-02T11:43:09Z
5.7 147 2021-12-02T11:43:08Z
5.6.51 98 2021-12-02T11:43:06Z
5.6 98 2021-12-02T11:43:04Z
5 147 2021-12-02T11:42:49Z
8.0.26 143 2021-10-12T16:42:51Z
5.7.35 147 2021-10-12T16:42:35Z
8.0.25 154 2021-06-23T07:31:50Z
5.7.34 147 2021-06-23T07:31:34Z
8.0.24 154 2021-04-19T19:14:47Z
5.6.27 106 2015-12-17T03:17:56Z
5.7.9 117 2015-12-09T01:30:39Z
5.5.46 83 2015-12-08T02:46:46Z
恭喜我們,功能上已經成功實現了。
但是有兩個小問題
- 沒有對齊,不美觀
- 結果亂序,不好找,上面8.x/5.x的版本混亂排列,我們需要排個序方便檢視
排序標籤結構
我們先來解決排序的問題
在這裡我們是要對 response.Results
這個切片進行排序,這屬於對自定義結構體排序,需要自己實現 sort.Interface
介面,就可以使用 sort.Sort
進行排序了。
我們調整一下結構體的定義:
type response struct {
Count int `json:"count"`
Next string `json:"next"`
Results responseResultSortable `json:"results"`
}
type responseResult struct {
Name string `json:"name"`
FullSize int `json:"full_size"`
V2 bool `json:"v2"`
TagStatus string `json:"tag_status"`
TagLastPulled time.Time `json:"tag_last_pulled"`
TagLastPushed time.Time `json:"tag_last_pushed"`
}
type responseResultSortable []responseResult
func (r responseResultSortable) Len() int {
return len(r)
}
func (r responseResultSortable) Less(i, j int) bool {
return r[i].Name > r[j].Name
}
func (r responseResultSortable) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
把之前的 []responseResult
拿出來,定義一個別名:
type responseResultSortable []responseResult
然後替換 response.Results
中的型別,然後給型別 responseResultSortable
實現 sort.Interface
的定義,就是三個方法:
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with index i
// must sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
最後需要再 parse
函式中呼叫一下排序:
func parse(name string, respBody []byte) {
respData := new(response)
err := json.Unmarshal(respBody, &respData)
if err != nil {
fmt.Printf("json.Unmarshal data err: %v\n", err)
return
}
if len(respData.Results) == 0 {
fmt.Printf("there is no tag data for name: %s\n", name)
return
}
// resort results
sort.Sort(respData.Results)
fmt.Printf("Tags for %s:\n\n", name)
fmt.Printf("%s %s %s\n", "TAG", "SIZE(MB)", "LASTPUSH")
for _, v := range respData.Results {
fmt.Printf("%s %10d %s\n",
v.Name,
v.FullSize/1024/1024,
v.TagLastPushed.Format(time.RFC3339),
)
}
}
重新編譯,執行,現在結果應該是這樣的:
Tags for mysql:
TAG SIZE(MB) LASTPUSH
latest 144 2021-12-02T11:43:26Z
8.0.27 144 2021-12-02T11:43:23Z
8.0.26 143 2021-10-12T16:42:51Z
8.0.25 154 2021-06-23T07:31:50Z
8.0.24 154 2021-04-19T19:14:47Z
8.0 144 2021-12-02T11:43:21Z
8 144 2021-12-02T11:43:20Z
5.7.36 147 2021-12-02T11:43:09Z
5.7.35 147 2021-10-12T16:42:35Z
5.7.34 147 2021-06-23T07:31:34Z
5.7.9 117 2015-12-09T01:30:39Z
5.7 147 2021-12-02T11:43:08Z
5.6.51 98 2021-12-02T11:43:06Z
5.6.27 106 2015-12-17T03:17:56Z
5.6 98 2021-12-02T11:43:04Z
5.5.46 83 2015-12-08T02:46:46Z
5 147 2021-12-02T11:42:49Z
好了,排序沒有問題了。
美化列印結果
我看了docker search的結果會根據名稱的長短自動對齊,我們也來嘗試實現一下。
原理就是,獲取最大的name
長度,給長度不足的補上N個空格,就可以了。
func parse(name string, respBody []byte) {
respData := new(response)
err := json.Unmarshal(respBody, &respData)
if err != nil {
fmt.Printf("json.Unmarshal data err: %v\n", err)
return
}
if len(respData.Results) == 0 {
fmt.Printf("there is no tag data for name: %s\n", name)
return
}
// resort results
sort.Sort(respData.Results)
// beauty format
var maxLengthForName int
for _, v := range respData.Results {
if len(v.Name) > maxLengthForName {
maxLengthForName = len(v.Name)
}
}
fmt.Printf("Tags for %s:\n\n", name)
fmt.Printf("%s%s%10s %s\n", "TAG", strings.Repeat(" ", maxLengthForName), "SIZE(MB)", "LASTPUSH")
for _, v := range respData.Results {
fmt.Printf("%s%s %10d %s\n",
v.Name, strings.Repeat(" ", maxLengthForName-len(v.Name)),
v.FullSize/1024/1024,
v.TagLastPushed.Format(time.RFC3339),
)
}
}
現在重新編譯執行:
./docker-search-tag mysql
結果應該是這樣的:
Tags for mysql:
TAG SIZE(MB) LASTPUSH
latest 144 2021-12-02T11:43:26Z
8.0.27 144 2021-12-02T11:43:23Z
8.0.26 143 2021-10-12T16:42:51Z
8.0.25 154 2021-06-23T07:31:50Z
8.0.24 154 2021-04-19T19:14:47Z
8.0 144 2021-12-02T11:43:21Z
8 144 2021-12-02T11:43:20Z
5.7.36 147 2021-12-02T11:43:09Z
5.7.35 147 2021-10-12T16:42:35Z
5.7.34 147 2021-06-23T07:31:34Z
5.7.9 117 2015-12-09T01:30:39Z
5.7 147 2021-12-02T11:43:08Z
5.6.51 98 2021-12-02T11:43:06Z
5.6.27 106 2015-12-17T03:17:56Z
5.6 98 2021-12-02T11:43:04Z
5.5.46 83 2015-12-08T02:46:46Z
5 147 2021-12-02T11:42:49Z
看起來完美,手工,提交github,最後的專案地址是:
https://github.com/safeie/docker-search-tag
感謝一起感受這個簡單過程的你?