mirror of
https://github.com/containers/podman.git
synced 2025-06-06 15:00:40 +08:00
Refactor podman search to be more code friendly
* JSON and API description fields are no longer truncated. Formatting moved to client, better support of MVP. * --no-trunc now defaults to true * Updated tests for changes Closes #11894 Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
@ -3,6 +3,7 @@ package images
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/common/pkg/auth"
|
"github.com/containers/common/pkg/auth"
|
||||||
"github.com/containers/common/pkg/completion"
|
"github.com/containers/common/pkg/completion"
|
||||||
@ -79,7 +80,7 @@ func searchFlags(cmd *cobra.Command) {
|
|||||||
|
|
||||||
filterFlagName := "filter"
|
filterFlagName := "filter"
|
||||||
flags.StringSliceVarP(&searchOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])")
|
flags.StringSliceVarP(&searchOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])")
|
||||||
//TODO add custom filter function
|
// TODO add custom filter function
|
||||||
_ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone)
|
_ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone)
|
||||||
|
|
||||||
formatFlagName := "format"
|
formatFlagName := "format"
|
||||||
@ -90,7 +91,7 @@ func searchFlags(cmd *cobra.Command) {
|
|||||||
flags.IntVar(&searchOptions.Limit, limitFlagName, 0, "Limit the number of results")
|
flags.IntVar(&searchOptions.Limit, limitFlagName, 0, "Limit the number of results")
|
||||||
_ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone)
|
_ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone)
|
||||||
|
|
||||||
flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output")
|
flags.Bool("no-trunc", true, "Do not truncate the output. Default: true")
|
||||||
|
|
||||||
authfileFlagName := "authfile"
|
authfileFlagName := "authfile"
|
||||||
flags.StringVar(&searchOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
flags.StringVar(&searchOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
||||||
@ -132,11 +133,20 @@ func imageSearch(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(searchReport) == 0 {
|
if len(searchReport) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
noTrunc, _ := cmd.Flags().GetBool("no-trunc")
|
||||||
|
isJSON := report.IsJSON(searchOptions.Format)
|
||||||
|
for i, element := range searchReport {
|
||||||
|
d := strings.ReplaceAll(element.Description, "\n", " ")
|
||||||
|
if len(d) > 44 && !(noTrunc || isJSON) {
|
||||||
|
d = strings.TrimSpace(d[:44]) + "..."
|
||||||
|
}
|
||||||
|
searchReport[i].Description = d
|
||||||
|
}
|
||||||
|
|
||||||
hdrs := report.Headers(entities.ImageSearchReport{}, nil)
|
hdrs := report.Headers(entities.ImageSearchReport{}, nil)
|
||||||
renderHeaders := true
|
renderHeaders := true
|
||||||
var row string
|
var row string
|
||||||
@ -145,12 +155,12 @@ func imageSearch(cmd *cobra.Command, args []string) error {
|
|||||||
if len(searchOptions.Filters) != 0 {
|
if len(searchOptions.Filters) != 0 {
|
||||||
return errors.Errorf("filters are not applicable to list tags result")
|
return errors.Errorf("filters are not applicable to list tags result")
|
||||||
}
|
}
|
||||||
if report.IsJSON(searchOptions.Format) {
|
if isJSON {
|
||||||
listTagsEntries := buildListTagsJSON(searchReport)
|
listTagsEntries := buildListTagsJSON(searchReport)
|
||||||
return printArbitraryJSON(listTagsEntries)
|
return printArbitraryJSON(listTagsEntries)
|
||||||
}
|
}
|
||||||
row = "{{.Name}}\t{{.Tag}}\n"
|
row = "{{.Name}}\t{{.Tag}}\n"
|
||||||
case report.IsJSON(searchOptions.Format):
|
case isJSON:
|
||||||
return printArbitraryJSON(searchReport)
|
return printArbitraryJSON(searchReport)
|
||||||
case cmd.Flags().Changed("format"):
|
case cmd.Flags().Changed("format"):
|
||||||
renderHeaders = report.HasTable(searchOptions.Format)
|
renderHeaders = report.HasTable(searchOptions.Format)
|
||||||
@ -190,7 +200,7 @@ func printArbitraryJSON(v interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildListTagsJSON(searchReport []entities.ImageSearchReport) []listEntryTag {
|
func buildListTagsJSON(searchReport []entities.ImageSearchReport) []listEntryTag {
|
||||||
entries := []listEntryTag{}
|
entries := make([]listEntryTag, 0)
|
||||||
|
|
||||||
ReportLoop:
|
ReportLoop:
|
||||||
for _, report := range searchReport {
|
for _, report := range searchReport {
|
||||||
|
@ -81,7 +81,7 @@ The result contains the Image name and its tag, one line for every tag associate
|
|||||||
|
|
||||||
#### **--no-trunc**
|
#### **--no-trunc**
|
||||||
|
|
||||||
Do not truncate the output (default *false*).
|
Do not truncate the output (default *true*).
|
||||||
|
|
||||||
#### **--tls-verify**
|
#### **--tls-verify**
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
query := struct {
|
query := struct {
|
||||||
Term string `json:"term"`
|
Term string `json:"term"`
|
||||||
Limit int `json:"limit"`
|
Limit int `json:"limit"`
|
||||||
NoTrunc bool `json:"noTrunc"`
|
|
||||||
Filters map[string][]string `json:"filters"`
|
Filters map[string][]string `json:"filters"`
|
||||||
TLSVerify bool `json:"tlsVerify"`
|
TLSVerify bool `json:"tlsVerify"`
|
||||||
ListTags bool `json:"listTags"`
|
ListTags bool `json:"listTags"`
|
||||||
@ -50,7 +49,6 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
options := entities.ImageSearchOptions{
|
options := entities.ImageSearchOptions{
|
||||||
Authfile: authfile,
|
Authfile: authfile,
|
||||||
Limit: query.Limit,
|
Limit: query.Limit,
|
||||||
NoTrunc: query.NoTrunc,
|
|
||||||
ListTags: query.ListTags,
|
ListTags: query.ListTags,
|
||||||
Filters: filters,
|
Filters: filters,
|
||||||
}
|
}
|
||||||
|
@ -1090,10 +1090,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
|
|||||||
// default: 25
|
// default: 25
|
||||||
// description: maximum number of results
|
// description: maximum number of results
|
||||||
// - in: query
|
// - in: query
|
||||||
// name: noTrunc
|
|
||||||
// type: boolean
|
|
||||||
// description: do not truncate any of the result strings
|
|
||||||
// - in: query
|
|
||||||
// name: filters
|
// name: filters
|
||||||
// type: string
|
// type: string
|
||||||
// description: |
|
// description: |
|
||||||
|
@ -133,8 +133,6 @@ type SearchOptions struct {
|
|||||||
Filters map[string][]string
|
Filters map[string][]string
|
||||||
// Limit the number of results.
|
// Limit the number of results.
|
||||||
Limit *int
|
Limit *int
|
||||||
// NoTrunc will not truncate the output.
|
|
||||||
NoTrunc *bool
|
|
||||||
// SkipTLSVerify to skip HTTPS and certificate verification.
|
// SkipTLSVerify to skip HTTPS and certificate verification.
|
||||||
SkipTLSVerify *bool
|
SkipTLSVerify *bool
|
||||||
// ListTags search the available tags of the repository
|
// ListTags search the available tags of the repository
|
||||||
|
@ -62,21 +62,6 @@ func (o *SearchOptions) GetLimit() int {
|
|||||||
return *o.Limit
|
return *o.Limit
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithNoTrunc set field NoTrunc to given value
|
|
||||||
func (o *SearchOptions) WithNoTrunc(value bool) *SearchOptions {
|
|
||||||
o.NoTrunc = &value
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNoTrunc returns value of field NoTrunc
|
|
||||||
func (o *SearchOptions) GetNoTrunc() bool {
|
|
||||||
if o.NoTrunc == nil {
|
|
||||||
var z bool
|
|
||||||
return z
|
|
||||||
}
|
|
||||||
return *o.NoTrunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSkipTLSVerify set field SkipTLSVerify to given value
|
// WithSkipTLSVerify set field SkipTLSVerify to given value
|
||||||
func (o *SearchOptions) WithSkipTLSVerify(value bool) *SearchOptions {
|
func (o *SearchOptions) WithSkipTLSVerify(value bool) *SearchOptions {
|
||||||
o.SkipTLSVerify = &value
|
o.SkipTLSVerify = &value
|
||||||
|
@ -218,8 +218,6 @@ type ImageSearchOptions struct {
|
|||||||
Filters []string
|
Filters []string
|
||||||
// Limit the number of results.
|
// Limit the number of results.
|
||||||
Limit int
|
Limit int
|
||||||
// NoTrunc will not truncate the output.
|
|
||||||
NoTrunc bool
|
|
||||||
// SkipTLSVerify to skip HTTPS and certificate verification.
|
// SkipTLSVerify to skip HTTPS and certificate verification.
|
||||||
SkipTLSVerify types.OptionalBool
|
SkipTLSVerify types.OptionalBool
|
||||||
// ListTags search the available tags of the repository
|
// ListTags search the available tags of the repository
|
||||||
|
@ -417,6 +417,7 @@ func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportO
|
|||||||
return &entities.ImageImportReport{Id: imageID}, nil
|
return &entities.ImageImportReport{Id: imageID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search for images using term and filters
|
||||||
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
||||||
filter, err := libimage.ParseSearchFilter(opts.Filters)
|
filter, err := libimage.ParseSearchFilter(opts.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -427,7 +428,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
|
|||||||
Authfile: opts.Authfile,
|
Authfile: opts.Authfile,
|
||||||
Filter: *filter,
|
Filter: *filter,
|
||||||
Limit: opts.Limit,
|
Limit: opts.Limit,
|
||||||
NoTrunc: opts.NoTrunc,
|
NoTrunc: true,
|
||||||
InsecureSkipTLSVerify: opts.SkipTLSVerify,
|
InsecureSkipTLSVerify: opts.SkipTLSVerify,
|
||||||
ListTags: opts.ListTags,
|
ListTags: opts.ListTags,
|
||||||
}
|
}
|
||||||
@ -454,7 +455,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
|
|||||||
return reports, nil
|
return reports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig returns a copy of the configuration used by the runtime
|
// Config returns a copy of the configuration used by the runtime
|
||||||
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
|
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
|
||||||
return ir.Libpod.GetConfig()
|
return ir.Libpod.GetConfig()
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/image/v5/docker/reference"
|
"github.com/containers/image/v5/docker/reference"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
images "github.com/containers/podman/v3/pkg/bindings/images"
|
"github.com/containers/podman/v3/pkg/bindings/images"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities/reports"
|
"github.com/containers/podman/v3/pkg/domain/entities/reports"
|
||||||
"github.com/containers/podman/v3/pkg/domain/utils"
|
"github.com/containers/podman/v3/pkg/domain/utils"
|
||||||
@ -323,7 +323,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
|
|||||||
|
|
||||||
options := new(images.SearchOptions)
|
options := new(images.SearchOptions)
|
||||||
options.WithAuthfile(opts.Authfile).WithFilters(mappedFilters).WithLimit(opts.Limit)
|
options.WithAuthfile(opts.Authfile).WithFilters(mappedFilters).WithLimit(opts.Limit)
|
||||||
options.WithListTags(opts.ListTags).WithNoTrunc(opts.NoTrunc)
|
options.WithListTags(opts.ListTags)
|
||||||
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
|
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
|
||||||
if s == types.OptionalBoolTrue {
|
if s == types.OptionalBoolTrue {
|
||||||
options.WithSkipTLSVerify(true)
|
options.WithSkipTLSVerify(true)
|
||||||
|
@ -2,6 +2,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
. "github.com/containers/podman/v3/test/utils"
|
. "github.com/containers/podman/v3/test/utils"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@ -123,6 +125,15 @@ registries = ['{{.Host}}:{{.Port}}']`
|
|||||||
Expect(search).Should(Exit(0))
|
Expect(search).Should(Exit(0))
|
||||||
Expect(search.IsJSONOutputValid()).To(BeTrue())
|
Expect(search.IsJSONOutputValid()).To(BeTrue())
|
||||||
Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/alpine"))
|
Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/alpine"))
|
||||||
|
|
||||||
|
// Test for https://github.com/containers/podman/issues/11894
|
||||||
|
contents := make([]entities.ImageSearchReport, 0)
|
||||||
|
err := json.Unmarshal(search.Out.Contents(), &contents)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(contents)).To(BeNumerically(">", 0), "No results from image search")
|
||||||
|
for _, element := range contents {
|
||||||
|
Expect(element.Description).ToNot(HaveSuffix("..."))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman search format json list tags", func() {
|
It("podman search format json list tags", func() {
|
||||||
@ -135,13 +146,17 @@ registries = ['{{.Host}}:{{.Port}}']`
|
|||||||
Expect(search.OutputToString()).To(ContainSubstring("2.7"))
|
Expect(search.OutputToString()).To(ContainSubstring("2.7"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman search no-trunc flag", func() {
|
// Test for https://github.com/containers/podman/issues/11894
|
||||||
search := podmanTest.Podman([]string{"search", "--no-trunc", "alpine"})
|
It("podman search no-trunc=false flag", func() {
|
||||||
|
search := podmanTest.Podman([]string{"search", "--no-trunc=false", "alpine", "--format={{.Description}}"})
|
||||||
search.WaitWithDefaultTimeout()
|
search.WaitWithDefaultTimeout()
|
||||||
Expect(search).Should(Exit(0))
|
Expect(search).Should(Exit(0))
|
||||||
Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
|
|
||||||
Expect(search.LineInOutputContains("docker.io/library/alpine")).To(BeTrue())
|
for _, line := range search.OutputToStringArray() {
|
||||||
Expect(search.LineInOutputContains("...")).To(BeFalse())
|
if len(line) > 44 {
|
||||||
|
Expect(line).To(HaveSuffix("..."), line+" should have been truncated")
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman search limit flag", func() {
|
It("podman search limit flag", func() {
|
||||||
|
Reference in New Issue
Block a user