背景
使用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
感谢一起感受这个简单过程的你😁
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。