Skip to content

Commit

Permalink
Feat: show available & status of local tags (#119)
Browse files Browse the repository at this point in the history
New command: sonar tags status

Shows the local tags for an image, and their status as compared to
Docker Hub. Status can be "local-only", synced, newer, or older.
  • Loading branch information
felicianotech committed Nov 15, 2021
1 parent cdc65c5 commit 4d9dc7d
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 4 deletions.
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ require (
github.com/arduino/go-apt-client v0.0.0-20190812130613-5613f843fdc8 // indirect
github.com/containerd/cgroups v1.0.1 // indirect
github.com/containerd/containerd v1.5.7 // indirect
github.com/docker/docker v20.10.8+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.10+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
Expand All @@ -48,9 +50,13 @@ require (
github.com/subosito/gotenv v1.2.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 // indirect
google.golang.org/grpc v1.40.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,12 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.8+incompatible h1:RVqD337BgQicVCzYrrlhLDWhq6OAD2PJDUg2LsEUvKM=
github.com/docker/docker v20.10.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.10+incompatible h1:GKkP0T7U4ks6X3lmmHKC2QDprnpRJor2Z5a8m62R9ZM=
github.com/docker/docker v20.10.10+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
Expand Down Expand Up @@ -886,6 +889,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -1178,6 +1182,7 @@ google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKr
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
Expand Down Expand Up @@ -1207,6 +1212,7 @@ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
Expand Down
2 changes: 1 addition & 1 deletion sonar/cmd/tags_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var (

tagsListCmd = &cobra.Command{
Use: "list <image-name>",
Short: "Displays tags for a given Docker image name",
Short: "Displays tags on Docker Hub for a given Docker image name",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {

Expand Down
124 changes: 124 additions & 0 deletions sonar/cmd/tags_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package cmd

import (
"context"
"fmt"
"strings"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/felicianotech/sonar/sonar/docker"
"github.com/spf13/cobra"
)

var statusCmd = &cobra.Command{
Use: "status <image-name>",
Short: "Displays the push/pull status of local tags",
Long: `Displays the tags for a particular image that you have locally in a table. Provides info on if the tag also exists on Docker Hub, and if it does, is the local or Hub version newer.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {

dCLI, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return err
}

images, err := dCLI.ImageList(context.Background(), types.ImageListOptions{})
if err != nil {
return err
}

var localTags []docker.Tag

// Loop through all images
for _, image := range images {

// Look through each image's tags
for _, tag := range image.RepoTags {

if !strings.HasPrefix(tag, args[0]) {
break
}

// this next section is to get the digest since while the image
// ID is available the digest is the preferred identifer.
var digestOrID string
if image.RepoDigests != nil {

digestOrID = strings.Split(image.RepoDigests[0], "@")[1]
} else {
digestOrID = image.ID
}

localTags = append(localTags, docker.Tag{
Name: strings.Split(tag, ":")[1],
Size: image.Size,
Date: time.Unix(image.Created, 0).UTC(),
Digest: digestOrID,
})
}
}

dCLI.Close()

// output data

if len(localTags) == 0 {
fmt.Println("The image doesn't have any tags local.")
return nil
}

hubTags, hubTagsErr := docker.GetAllTags(args[0])

fmt.Printf("Local tags for %s:\n\n", args[0])
fmt.Println(" Tag Docker Hub Status")
fmt.Println("========== ===================")
for _, tag := range localTags {

status := "local-only"

if hubTagsErr == nil {

for _, hubTag := range hubTags {
if tag.Name == hubTag.Name {

if hubTag.Digest == "" {
status = "can't check"
} else if tag.Digest == hubTag.Digest {
status = "synced"
} else if tag.Date.After(hubTag.Date) {
status = "newer"
} else if tag.Date.Equal(hubTag.Date) {
status = "synced"
} else if tag.Date.Before(hubTag.Date) {
status = "older"
}

break
}
}
}

fmt.Printf(" %-10s %5s\n", tag.Name, status)
}

// compare digest and.... creation time?

return nil
},
}

func init() {
tagsCmd.AddCommand(statusCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// statusCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// statusCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
4 changes: 2 additions & 2 deletions sonar/docker/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

type Tag struct {
Name string
Size uint64
Size int64
Date time.Time
Digest string
}
Expand Down Expand Up @@ -40,7 +40,7 @@ func GetAllTags(image string) ([]Tag, error) {
var aTag Tag

aTag.Name = v.(map[string]interface{})["name"].(string)
aTag.Size = uint64(v.(map[string]interface{})["full_size"].(float64))
aTag.Size = int64(v.(map[string]interface{})["full_size"].(float64))
aTag.Date, err = time.Parse(time.RFC3339, v.(map[string]interface{})["last_updated"].(string))
anImage := v.(map[string]interface{})["images"].([]interface{})[0]

Expand Down

0 comments on commit 4d9dc7d

Please sign in to comment.