背景
应用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) LASTPUSHlatest 144 2021-12-02T11:43:26Z8.0.4-rc 83 2018-03-14T08:42:43Z8.0.4 83 2018-03-14T08:42:43Z8.0.3 107 2018-02-17T09:43:00Z8.0.1 86 2017-06-24T13:36:11Z8.0.0 126 2017-04-10T23:29:13Z8.0 144 2021-12-02T11:43:21Z8 144 2021-12-02T11:43:20Z5.7.36 147 2021-12-02T11:43:09Z5.7.35 147 2021-10-12T16:42:35Z5.6.28 106 2016-02-02T22:41:46Z5.6.27 106 2015-12-17T03:17:56Z5.6 98 2021-12-02T11:43:04Z5.5 63 2019-05-10T23:43:32Z5 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) LASTPUSHlatest 144 2021-12-02T11:43:26Z8.0.27 144 2021-12-02T11:43:23Z8.0 144 2021-12-02T11:43:21Z8 144 2021-12-02T11:43:20Z5.7.36 147 2021-12-02T11:43:09Z5.7 147 2021-12-02T11:43:08Z5.6.51 98 2021-12-02T11:43:06Z5.6 98 2021-12-02T11:43:04Z5 147 2021-12-02T11:42:49Z8.0.26 143 2021-10-12T16:42:51Z5.7.35 147 2021-10-12T16:42:35Z8.0.25 154 2021-06-23T07:31:50Z5.7.34 147 2021-06-23T07:31:34Z8.0.24 154 2021-04-19T19:14:47Z5.6.27 106 2015-12-17T03:17:56Z5.7.9 117 2015-12-09T01:30:39Z5.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 []responseResultfunc (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) LASTPUSHlatest 144 2021-12-02T11:43:26Z8.0.27 144 2021-12-02T11:43:23Z8.0.26 143 2021-10-12T16:42:51Z8.0.25 154 2021-06-23T07:31:50Z8.0.24 154 2021-04-19T19:14:47Z8.0 144 2021-12-02T11:43:21Z8 144 2021-12-02T11:43:20Z5.7.36 147 2021-12-02T11:43:09Z5.7.35 147 2021-10-12T16:42:35Z5.7.34 147 2021-06-23T07:31:34Z5.7.9 117 2015-12-09T01:30:39Z5.7 147 2021-12-02T11:43:08Z5.6.51 98 2021-12-02T11:43:06Z5.6.27 106 2015-12-17T03:17:56Z5.6 98 2021-12-02T11:43:04Z5.5.46 83 2015-12-08T02:46:46Z5 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) LASTPUSHlatest 144 2021-12-02T11:43:26Z8.0.27 144 2021-12-02T11:43:23Z8.0.26 143 2021-10-12T16:42:51Z8.0.25 154 2021-06-23T07:31:50Z8.0.24 154 2021-04-19T19:14:47Z8.0 144 2021-12-02T11:43:21Z8 144 2021-12-02T11:43:20Z5.7.36 147 2021-12-02T11:43:09Z5.7.35 147 2021-10-12T16:42:35Z5.7.34 147 2021-06-23T07:31:34Z5.7.9 117 2015-12-09T01:30:39Z5.7 147 2021-12-02T11:43:08Z5.6.51 98 2021-12-02T11:43:06Z5.6.27 106 2015-12-17T03:17:56Z5.6 98 2021-12-02T11:43:04Z5.5.46 83 2015-12-08T02:46:46Z5 147 2021-12-02T11:42:49Z
看起来完满,手工,提交github,最初的我的项目地址是:
https://github.com/safeie/docker-search-tag
感激一起感触这个简略过程的你