mirror of https://github.com/containers/podman.git
Merge pull request #7836 from QiWang19/search-tags
Search repository tags using --list-tags
This commit is contained in:
commit
212011f166
|
@ -85,6 +85,7 @@ func searchFlags(flags *pflag.FlagSet) {
|
|||
flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output")
|
||||
flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
||||
flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
|
||||
flags.BoolVar(&searchOptions.ListTags, "list-tags", false, "List the tags of the input registry")
|
||||
}
|
||||
|
||||
// imageSearch implements the command for searching images.
|
||||
|
@ -101,6 +102,10 @@ func imageSearch(cmd *cobra.Command, args []string) error {
|
|||
return errors.Errorf("Limit %d is outside the range of [1, 100]", searchOptions.Limit)
|
||||
}
|
||||
|
||||
if searchOptions.ListTags && len(searchOptions.Filters) != 0 {
|
||||
return errors.Errorf("filters are not applicable to list tags result")
|
||||
}
|
||||
|
||||
// TLS verification in c/image is controlled via a `types.OptionalBool`
|
||||
// which allows for distinguishing among set-true, set-false, unspecified
|
||||
// which is important to implement a sane way of dealing with defaults of
|
||||
|
@ -119,12 +124,19 @@ func imageSearch(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(searchReport) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
hdrs := report.Headers(entities.ImageSearchReport{}, nil)
|
||||
row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n"
|
||||
if searchOptions.ListTags {
|
||||
if len(searchOptions.Filters) != 0 {
|
||||
return errors.Errorf("filters are not applicable to list tags result")
|
||||
}
|
||||
row = "{{.Name}}\t{{.Tag}}\n"
|
||||
}
|
||||
if cmd.Flags().Changed("format") {
|
||||
row = report.NormalizeFormat(searchOptions.Format)
|
||||
}
|
||||
|
|
|
@ -2024,6 +2024,7 @@ _podman_search() {
|
|||
--help
|
||||
-h
|
||||
--no-trunc
|
||||
--list-tags
|
||||
"
|
||||
_complete_ "$options_with_args" "$boolean_options"
|
||||
}
|
||||
|
|
|
@ -56,6 +56,9 @@ Valid placeholders for the Go template are listed below:
|
|||
| .Stars | Star count of image |
|
||||
| .Official | "[OK]" if image is official |
|
||||
| .Automated | "[OK]" if image is automated |
|
||||
| .Tag | Repository tag |
|
||||
|
||||
Note: use .Tag only if the --list-tags is set.
|
||||
|
||||
**--limit**=*limit*
|
||||
|
||||
|
@ -65,6 +68,12 @@ Example if limit is 10 and two registries are being searched, the total
|
|||
number of results will be 20, 10 from each (if there are at least 10 matches in each).
|
||||
The order of the search results is the order in which the API endpoint returns the results.
|
||||
|
||||
**--list-tags**
|
||||
|
||||
List the available tags in the repository for the specified image.
|
||||
**Note:** --list-tags requires the search term to be a fully specified image name.
|
||||
The result contains the Image name and its tag, one line for every tag associated with the image.
|
||||
|
||||
**--no-trunc**
|
||||
|
||||
Do not truncate the output
|
||||
|
@ -140,6 +149,15 @@ fedoraproject.org registry.fedoraproject.org/f25/kubernetes-proxy
|
|||
fedoraproject.org registry.fedoraproject.org/f25/kubernetes-scheduler 0
|
||||
fedoraproject.org registry.fedoraproject.org/f25/mariadb 0
|
||||
```
|
||||
|
||||
```
|
||||
$ podman search --list-tags registry.redhat.io/rhel
|
||||
NAME TAG
|
||||
registry.redhat.io/rhel 7.3-74
|
||||
registry.redhat.io/rhel 7.6-301
|
||||
registry.redhat.io/rhel 7.1-9
|
||||
...
|
||||
```
|
||||
Note: This works only with registries that implement the v2 API. If tried with a v1 registry an error will be returned.
|
||||
|
||||
## FILES
|
||||
|
|
|
@ -2,11 +2,13 @@ package image
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/image/v5/docker"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
sysreg "github.com/containers/podman/v2/pkg/registries"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -34,6 +36,8 @@ type SearchResult struct {
|
|||
Official string
|
||||
// Automated indicates if the image was created by an automated build.
|
||||
Automated string
|
||||
// Tag is the image tag
|
||||
Tag string
|
||||
}
|
||||
|
||||
// SearchOptions are used to control the behaviour of SearchImages.
|
||||
|
@ -49,6 +53,8 @@ type SearchOptions struct {
|
|||
Authfile string
|
||||
// InsecureSkipTLSVerify allows to skip TLS verification.
|
||||
InsecureSkipTLSVerify types.OptionalBool
|
||||
// ListTags returns the search result with available tags
|
||||
ListTags bool
|
||||
}
|
||||
|
||||
// SearchFilter allows filtering the results of SearchImages.
|
||||
|
@ -147,6 +153,15 @@ func searchImageInRegistry(term string, registry string, options SearchOptions)
|
|||
// every types.SystemContext, and to compute the value just once in one
|
||||
// place.
|
||||
sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath()
|
||||
if options.ListTags {
|
||||
results, err := searchRepositoryTags(registry, term, sc, options)
|
||||
if err != nil {
|
||||
logrus.Errorf("error listing registry tags %q: %v", registry, err)
|
||||
return []SearchResult{}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
results, err := docker.SearchRegistry(context.TODO(), sc, registry, term, limit)
|
||||
if err != nil {
|
||||
logrus.Errorf("error searching registry %q: %v", registry, err)
|
||||
|
@ -207,6 +222,42 @@ func searchImageInRegistry(term string, registry string, options SearchOptions)
|
|||
return paramsArr
|
||||
}
|
||||
|
||||
func searchRepositoryTags(registry, term string, sc *types.SystemContext, options SearchOptions) ([]SearchResult, error) {
|
||||
dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
|
||||
imageRef, err := alltransports.ParseImageName(fmt.Sprintf("%s/%s", registry, term))
|
||||
if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
|
||||
return nil, errors.Errorf("reference %q must be a docker reference", term)
|
||||
} else if err != nil {
|
||||
imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, fmt.Sprintf("%s/%s", registry, term)))
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("reference %q must be a docker reference", term)
|
||||
}
|
||||
}
|
||||
tags, err := docker.GetRepositoryTags(context.TODO(), sc, imageRef)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error getting repository tags: %v", err)
|
||||
}
|
||||
limit := maxQueries
|
||||
if len(tags) < limit {
|
||||
limit = len(tags)
|
||||
}
|
||||
if options.Limit != 0 {
|
||||
limit = len(tags)
|
||||
if options.Limit < limit {
|
||||
limit = options.Limit
|
||||
}
|
||||
}
|
||||
paramsArr := []SearchResult{}
|
||||
for i := 0; i < limit; i++ {
|
||||
params := SearchResult{
|
||||
Name: imageRef.DockerReference().Name(),
|
||||
Tag: tags[i],
|
||||
}
|
||||
paramsArr = append(paramsArr, params)
|
||||
}
|
||||
return paramsArr, nil
|
||||
}
|
||||
|
||||
// ParseSearchFilter turns the filter into a SearchFilter that can be used for
|
||||
// searching images.
|
||||
func ParseSearchFilter(filter []string) (*SearchFilter, error) {
|
||||
|
|
|
@ -608,6 +608,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|||
NoTrunc bool `json:"noTrunc"`
|
||||
Filters []string `json:"filters"`
|
||||
TLSVerify bool `json:"tlsVerify"`
|
||||
ListTags bool `json:"listTags"`
|
||||
}{
|
||||
// This is where you can override the golang default value for one of fields
|
||||
}
|
||||
|
@ -618,8 +619,9 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
options := image.SearchOptions{
|
||||
Limit: query.Limit,
|
||||
NoTrunc: query.NoTrunc,
|
||||
Limit: query.Limit,
|
||||
NoTrunc: query.NoTrunc,
|
||||
ListTags: query.ListTags,
|
||||
}
|
||||
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||
options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||
|
@ -650,6 +652,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|||
reports[i].Stars = searchResults[i].Stars
|
||||
reports[i].Official = searchResults[i].Official
|
||||
reports[i].Automated = searchResults[i].Automated
|
||||
reports[i].Tag = searchResults[i].Tag
|
||||
}
|
||||
|
||||
utils.WriteResponse(w, http.StatusOK, reports)
|
||||
|
|
|
@ -169,6 +169,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
|
|||
// - `is-automated=(true|false)`
|
||||
// - `is-official=(true|false)`
|
||||
// - `stars=<number>` Matches images that has at least 'number' stars.
|
||||
// - in: query
|
||||
// name: listTags
|
||||
// type: boolean
|
||||
// description: list the available tags in the repository
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
|
|
|
@ -314,6 +314,7 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions)
|
|||
params.Set("term", term)
|
||||
params.Set("limit", strconv.Itoa(opts.Limit))
|
||||
params.Set("noTrunc", strconv.FormatBool(opts.NoTrunc))
|
||||
params.Set("listTags", strconv.FormatBool(opts.ListTags))
|
||||
for _, f := range opts.Filters {
|
||||
params.Set("filters", f)
|
||||
}
|
||||
|
|
|
@ -214,6 +214,8 @@ type ImageSearchOptions struct {
|
|||
NoTrunc bool
|
||||
// SkipTLSVerify to skip HTTPS and certificate verification.
|
||||
SkipTLSVerify types.OptionalBool
|
||||
// ListTags search the available tags of the repository
|
||||
ListTags bool
|
||||
}
|
||||
|
||||
// ImageSearchReport is the response from searching images.
|
||||
|
@ -230,6 +232,8 @@ type ImageSearchReport struct {
|
|||
Official string
|
||||
// Automated indicates if the image was created by an automated build.
|
||||
Automated string
|
||||
// Tag is the repository tag
|
||||
Tag string
|
||||
}
|
||||
|
||||
// Image List Options
|
||||
|
|
|
@ -511,6 +511,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
|
|||
Limit: opts.Limit,
|
||||
NoTrunc: opts.NoTrunc,
|
||||
InsecureSkipTLSVerify: opts.SkipTLSVerify,
|
||||
ListTags: opts.ListTags,
|
||||
}
|
||||
|
||||
searchResults, err := image.SearchImages(term, searchOpts)
|
||||
|
@ -529,6 +530,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
|
|||
reports[i].Stars = searchResults[i].Stars
|
||||
reports[i].Official = searchResults[i].Official
|
||||
reports[i].Automated = searchResults[i].Automated
|
||||
reports[i].Tag = searchResults[i].Tag
|
||||
}
|
||||
|
||||
return reports, nil
|
||||
|
|
|
@ -423,4 +423,24 @@ registries = ['{{.Host}}:{{.Port}}']`
|
|||
Expect(search.ExitCode()).To(Equal(0))
|
||||
Expect(len(search.OutputToStringArray()) > 1).To(BeTrue())
|
||||
})
|
||||
|
||||
It("podman search repository tags", func() {
|
||||
search := podmanTest.Podman([]string{"search", "--list-tags", "--limit", "30", "docker.io/library/alpine"})
|
||||
search.WaitWithDefaultTimeout()
|
||||
Expect(search.ExitCode()).To(Equal(0))
|
||||
Expect(len(search.OutputToStringArray())).To(Equal(31))
|
||||
|
||||
search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/alpine"})
|
||||
search.WaitWithDefaultTimeout()
|
||||
Expect(search.ExitCode()).To(Equal(0))
|
||||
Expect(len(search.OutputToStringArray()) > 2).To(BeTrue())
|
||||
|
||||
search = podmanTest.Podman([]string{"search", "--filter=is-official", "--list-tags", "docker.io/library/alpine"})
|
||||
search.WaitWithDefaultTimeout()
|
||||
Expect(search.ExitCode()).To(Not(Equal(0)))
|
||||
|
||||
search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/"})
|
||||
search.WaitWithDefaultTimeout()
|
||||
Expect(len(search.OutputToStringArray()) == 0).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue