background
Use docker search mysql
to show which mirrors are available, but not which tag
, but when we use docker pull mysql:TAG
download the mirrors, we must specify the label, and often we have to specify a specific version, the label can only be found from the docker hub:
https://hub.docker.com/_/mysql?tab=tags
This is too tiring, let's start a project to query the label of the specified mirror directly on the command line.
Example
See the final result:
https://github.com/safeie/docker-search-tag
docker-search-tag mysql
show result:
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
The basic idea is that the query page here on hub.docker.com provides JSON results, and we can print out the desired results by analyzing the JSON data.
coding
Construct command line parameters
We hope that the command line parameters we construct are like this:
Usage: docker-search-tag NAME [NUM]
Two parameters are supported:
- NAME mirror name
- NUM number of tags, optional parameters
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)
}
Judge the input parameters by os.Args
, the zeroth parameter is the program itself, the first is NAME
, the second is NUM
, the second parameter can be omitted, if there is no input parameter, then give a prompt and terminate the program.
Request HTTP data
Construct URL and request data
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)
}
Parse JSON data
Parsing data in Go is not as simple as PHP/Python. You need to define the structure first and parse the json data directly into the structure.
The simplified structure returned by docker hub looks like this:
{
"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"
}
]
}
We only focus on specific fields, the number of tags returned, the requested url and tag list. Specific to the tag structure, we care about the tag name, image size, update time, etc. The corresponding structure is defined as follows:
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"`
}
After defining the structure, we can parse the structure and print the data we want:
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),
)
}
}
The overall structure is complete, then main
function last call request(name, num)
, in request
function last call parse(name, respBody)
have debugging.
You need to initialize go mod first, otherwise it won't compilego mod init github.com/safeie/docker-search-tag
go mod tidy
At this point we can print out the required tag information, let's compile and test:
go build
./docker-search-tag mysql
The result should look like this:
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
Congratulations, the function has been successfully implemented.
But there are two small problems
- Not aligned, unsightly
- The results are out of order and difficult to find. The 8.x/5.x versions above are arranged in disorder, we need to sort them in order to facilitate viewing
Sort label structure
Let's solve the sorting problem first
Here we are going to response.Results
the slice 061b9765e0eeac, which belongs to the sorting of a custom structure. You need to implement the sort.Interface
interface yourself, and you can use sort.Sort
for sorting.
Let's adjust the definition of structure:
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]
}
Take out the previous []responseResult
and define an alias:
type responseResultSortable []responseResult
Then replace response.Results
type, and then to type responseResultSortable
achieve sort.Interface
defined, is the three methods:
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)
}
Finally, you need to call the sort in the 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),
)
}
}
Recompile, execute, and now the result should be like this:
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
Okay, there is no problem with sorting.
Beautify print results
I saw that the results of docker search will be automatically aligned according to the length of the name, let's try to achieve it.
The principle is to obtain the maximum name
, and fill in N spaces for those with insufficient length.
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),
)
}
}
Now recompile and execute:
./docker-search-tag mysql
The result should look like this:
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
It looks perfect, manual, submitted to github, the final project address is:
https://github.com/safeie/docker-search-tag
Thank you for feeling this simple process together😁
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。