Skip to content

Commit

Permalink
feat: pull completion using hub
Browse files Browse the repository at this point in the history
Signed-off-by: Alano Terblanche <[email protected]>
  • Loading branch information
Benehiko committed Oct 11, 2024
1 parent 88f1e99 commit f4c164d
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 3 deletions.
124 changes: 124 additions & 0 deletions cli/command/completion/functions.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package completion

import (
"encoding/json"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -189,3 +194,122 @@ var commonPlatforms = []string{
func Platforms(_ *cobra.Command, _ []string, _ string) (platforms []string, _ cobra.ShellCompDirective) {
return commonPlatforms, cobra.ShellCompDirectiveNoFileComp
}

type ImageSearchResult struct {
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ShortDesc string `json:"short_description"`
Source string `json:"source"`
StarCount int `json:"star_count"`
}

type ImageSearch struct {
Totals int `json:"totals"`
Results []ImageSearchResult `json:"results"`
}

type Image struct {
ID int `json:"id"`
Name string `json:"name"`
TagStatus string `json:"tag_status"`
V2 bool `json:"v2"`
Digest string `json:"digest"`
LastUpdated time.Time `json:"last_updated"`
LastUpdater int `json:"last_updater"`
Creator int `json:"creator"`
Repository int `json:"repository"`
}
type ImageTags struct {
Count int `json:"count"`
Next string `json:"next"`
Prev string `json:"prev"`
Results []Image `json:"results"`
}

func Images(cmd *cobra.Command, arg []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ctx := cmd.Context()
c := &http.Client{
Timeout: 2 * time.Second,
}

if imageName, imageTag, ok := strings.Cut(toComplete, ":"); ok {
u, err := url.Parse("https://hub.docker.com/v2/repositories/library/" + imageName + "/tags/")
if err != nil {
logrus.Errorf("Error parsing hub image tags URL: %v", err)
return nil, cobra.ShellCompDirectiveError
}
q := u.Query()
q.Set("ordering", "last_updated")
q.Set("page_size", "25")
q.Set("name", imageTag)
u.RawQuery = q.Encode()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
logrus.Errorf("Error creating hub image tags request: %v", err)
return nil, cobra.ShellCompDirectiveError
}

resp, err := c.Do(req)
if err != nil {
logrus.Errorf("Error sending hub image tags request: %v", err)
return nil, cobra.ShellCompDirectiveError
}

defer resp.Body.Close()

var tags *ImageTags
if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
logrus.Errorf("Error decoding hub image tags response: %v", err)
return nil, cobra.ShellCompDirectiveError
}

names := make([]string, 0, len(tags.Results))
for _, i := range tags.Results {
names = append(names, imageName+":"+i.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}

u, err := url.Parse("https://hub.docker.com/api/search/v3/catalog/search")
if err != nil {
logrus.Errorf("Error parsing hub image search URL: %v", err)
return nil, cobra.ShellCompDirectiveNoFileComp
}
q := u.Query()
q.Set("query", toComplete)
q.Set("extension_reviewed", "")
q.Set("from", "0")
q.Set("size", "25")
u.RawQuery = q.Encode()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
logrus.Errorf("Error creating hub image search request: %v", err)
return nil, cobra.ShellCompDirectiveNoFileComp
}

resp, err := c.Do(req)
if err != nil {
logrus.Errorf("Error sending hub image search request: %v", err)
return nil, cobra.ShellCompDirectiveNoFileComp
}
defer resp.Body.Close()

var images *ImageSearch
if err := json.NewDecoder(resp.Body).Decode(&images); err != nil {
logrus.Errorf("Error decoding hub image search response: %v", err)
return nil, cobra.ShellCompDirectiveNoFileComp
}

names := make([]string, 0, len(images.Results))
for _, i := range images.Results {
names = append(names, i.Name)
}

return names, cobra.ShellCompDirectiveNoFileComp
}
28 changes: 27 additions & 1 deletion cli/command/container/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,33 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
}
return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
},
ValidArgsFunction: completion.ImageNames(dockerCli),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
unique := map[string]struct{}{}
localImages, shellComp := completion.ImageNames(dockerCli)(cmd, args, toComplete)

var all []string
if shellComp != cobra.ShellCompDirectiveError {
all = make([]string, 0, len(localImages))
for _, img := range localImages {
unique[img] = struct{}{}
all = append(all, fmt.Sprintf("%s\tlocal", img))
}
}

remoteImages, shellCompRemote := completion.Images(cmd, args, toComplete)
if shellCompRemote != cobra.ShellCompDirectiveError {
if len(all) == 0 {
all = make([]string, 0, len(remoteImages))
}
for _, img := range remoteImages {
if _, ok := unique[img]; !ok {
all = append(all, fmt.Sprintf("%s\tremote", img))
}
}
}

return all, cobra.ShellCompDirectiveKeepOrder | cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveDefault
},
Annotations: map[string]string{
"category-top": "1",
"aliases": "docker container run, docker run",
Expand Down
7 changes: 6 additions & 1 deletion cli/command/image/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command {
"category-top": "5",
"aliases": "docker image pull, docker pull",
},
ValidArgsFunction: completion.NoComplete,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return completion.Images(cmd, args, toComplete)
},
}

flags := cmd.Flags()
Expand Down
2 changes: 1 addition & 1 deletion scripts/build/binary
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ if [ "$(go env GOOS)" = "windows" ]; then
fi
fi

(set -x ; go build -o "${TARGET}" -tags "${GO_BUILDTAGS}" -ldflags "${GO_LDFLAGS}" ${GO_BUILDMODE} "${SOURCE}")
(set -x ; go build -o "${TARGET}" -tags "${GO_BUILDTAGS}" -ldflags "${GO_LDFLAGS}" ${GO_BUILDMODE} -buildvcs=false "${SOURCE}")

ln -sf "$(basename "${TARGET}")" "$(dirname "${TARGET}")/docker"

0 comments on commit f4c164d

Please sign in to comment.