背景

应用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

祝贺咱们,性能上曾经胜利实现了。

然而有两个小问题

  1. 没有对齐,不美观
  2. 后果乱序,不好找,下面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

感激一起感触这个简略过程的你