mirror of https://github.com/containers/podman.git
Merge pull request #5709 from vrothberg/v2-search
podmanV2: implement search
This commit is contained in:
commit
85c352d8bc
|
@ -0,0 +1,157 @@
|
||||||
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
buildahcli "github.com/containers/buildah/pkg/cli"
|
||||||
|
"github.com/containers/buildah/pkg/formats"
|
||||||
|
"github.com/containers/image/v5/types"
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/registry"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/util/camelcase"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// searchOptionsWrapper wraps entities.ImagePullOptions and prevents leaking
|
||||||
|
// CLI-only fields into the API types.
|
||||||
|
type searchOptionsWrapper struct {
|
||||||
|
entities.ImageSearchOptions
|
||||||
|
// CLI only flags
|
||||||
|
TLSVerifyCLI bool // Used to convert to an optional bool later
|
||||||
|
Format string // For go templating
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
searchOptions = searchOptionsWrapper{}
|
||||||
|
searchDescription = `Search registries for a given image. Can search all the default registries or a specific registry.
|
||||||
|
|
||||||
|
Users can limit the number of results, and filter the output based on certain conditions.`
|
||||||
|
|
||||||
|
// Command: podman search
|
||||||
|
searchCmd = &cobra.Command{
|
||||||
|
Use: "search [flags] TERM",
|
||||||
|
Short: "Search registry for image",
|
||||||
|
Long: searchDescription,
|
||||||
|
PreRunE: preRunE,
|
||||||
|
RunE: imageSearch,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Example: `podman search --filter=is-official --limit 3 alpine
|
||||||
|
podman search registry.fedoraproject.org/ # only works with v2 registries
|
||||||
|
podman search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command: podman image search
|
||||||
|
imageSearchCmd = &cobra.Command{
|
||||||
|
Use: searchCmd.Use,
|
||||||
|
Short: searchCmd.Short,
|
||||||
|
Long: searchCmd.Long,
|
||||||
|
PreRunE: searchCmd.PreRunE,
|
||||||
|
RunE: searchCmd.RunE,
|
||||||
|
Args: searchCmd.Args,
|
||||||
|
Example: `podman image search --filter=is-official --limit 3 alpine
|
||||||
|
podman image search registry.fedoraproject.org/ # only works with v2 registries
|
||||||
|
podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// search
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: searchCmd,
|
||||||
|
})
|
||||||
|
|
||||||
|
searchCmd.SetHelpTemplate(registry.HelpTemplate())
|
||||||
|
searchCmd.SetUsageTemplate(registry.UsageTemplate())
|
||||||
|
flags := searchCmd.Flags()
|
||||||
|
searchFlags(flags)
|
||||||
|
|
||||||
|
// images search
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: imageSearchCmd,
|
||||||
|
Parent: imageCmd,
|
||||||
|
})
|
||||||
|
|
||||||
|
imageSearchFlags := imageSearchCmd.Flags()
|
||||||
|
searchFlags(imageSearchFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchFlags set the flags for the pull command.
|
||||||
|
func searchFlags(flags *pflag.FlagSet) {
|
||||||
|
flags.StringSliceVarP(&searchOptions.Filters, "filter", "f", []string{}, "Filter output based on conditions provided (default [])")
|
||||||
|
flags.StringVar(&searchOptions.Format, "format", "", "Change the output format to a Go template")
|
||||||
|
flags.IntVar(&searchOptions.Limit, "limit", 0, "Limit the number of results")
|
||||||
|
flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output")
|
||||||
|
flags.StringVar(&searchOptions.Authfile, "authfile", buildahcli.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")
|
||||||
|
if registry.IsRemote() {
|
||||||
|
_ = flags.MarkHidden("authfile")
|
||||||
|
_ = flags.MarkHidden("tls-verify")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageSearch implements the command for searching images.
|
||||||
|
func imageSearch(cmd *cobra.Command, args []string) error {
|
||||||
|
searchTerm := ""
|
||||||
|
switch len(args) {
|
||||||
|
case 1:
|
||||||
|
searchTerm = args[0]
|
||||||
|
default:
|
||||||
|
return errors.Errorf("search requires exactly one argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
sarchOptsAPI := searchOptions.ImageSearchOptions
|
||||||
|
// 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
|
||||||
|
// boolean CLI flags.
|
||||||
|
if cmd.Flags().Changed("tls-verify") {
|
||||||
|
sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
format := genSearchFormat(searchOptions.Format)
|
||||||
|
if len(searchReport) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := formats.StdoutTemplateArray{Output: searchToGeneric(searchReport), Template: format, Fields: searchHeaderMap()}
|
||||||
|
return out.Out()
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchHeaderMap returns the headers of a SearchResult.
|
||||||
|
func searchHeaderMap() map[string]string {
|
||||||
|
s := new(entities.ImageSearchReport)
|
||||||
|
v := reflect.Indirect(reflect.ValueOf(s))
|
||||||
|
values := make(map[string]string, v.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
key := v.Type().Field(i).Name
|
||||||
|
value := key
|
||||||
|
values[key] = strings.ToUpper(strings.Join(camelcase.Split(value), " "))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func genSearchFormat(format string) string {
|
||||||
|
if format != "" {
|
||||||
|
// "\t" from the command line is not being recognized as a tab
|
||||||
|
// replacing the string "\t" to a tab character if the user passes in "\t"
|
||||||
|
return strings.Replace(format, `\t`, "\t", -1)
|
||||||
|
}
|
||||||
|
return "table {{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchToGeneric(params []entities.ImageSearchReport) (genericParams []interface{}) {
|
||||||
|
for _, v := range params {
|
||||||
|
genericParams = append(genericParams, interface{}(v))
|
||||||
|
}
|
||||||
|
return genericParams
|
||||||
|
}
|
|
@ -57,6 +57,7 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
||||||
Filter: filter,
|
Filter: filter,
|
||||||
Limit: query.Limit,
|
Limit: query.Limit,
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := image.SearchImages(query.Term, options)
|
results, err := image.SearchImages(query.Term, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.BadRequest(w, "term", query.Term, err)
|
utils.BadRequest(w, "term", query.Term, err)
|
||||||
|
|
|
@ -645,3 +645,56 @@ func UntagImage(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
utils.WriteResponse(w, http.StatusCreated, "")
|
utils.WriteResponse(w, http.StatusCreated, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SearchImages(w http.ResponseWriter, r *http.Request) {
|
||||||
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
|
query := struct {
|
||||||
|
Term string `json:"term"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Filters []string `json:"filters"`
|
||||||
|
TLSVerify bool `json:"tlsVerify"`
|
||||||
|
}{
|
||||||
|
// This is where you can override the golang default value for one of fields
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||||
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
options := image.SearchOptions{
|
||||||
|
Limit: query.Limit,
|
||||||
|
}
|
||||||
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||||
|
options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := r.URL.Query()["filters"]; found {
|
||||||
|
filter, err := image.ParseSearchFilter(query.Filters)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse filters parameter for %s", r.URL.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
options.Filter = *filter
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResults, err := image.SearchImages(query.Term, options)
|
||||||
|
if err != nil {
|
||||||
|
utils.BadRequest(w, "term", query.Term, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Convert from image.SearchResults to entities.ImageSearchReport. We don't
|
||||||
|
// want to leak any low-level packages into the remote client, which
|
||||||
|
// requires converting.
|
||||||
|
reports := make([]entities.ImageSearchReport, len(searchResults))
|
||||||
|
for i := range searchResults {
|
||||||
|
reports[i].Index = searchResults[i].Index
|
||||||
|
reports[i].Name = searchResults[i].Name
|
||||||
|
reports[i].Description = searchResults[i].Index
|
||||||
|
reports[i].Stars = searchResults[i].Stars
|
||||||
|
reports[i].Official = searchResults[i].Official
|
||||||
|
reports[i].Automated = searchResults[i].Automated
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.WriteResponse(w, http.StatusOK, reports)
|
||||||
|
}
|
||||||
|
|
|
@ -919,7 +919,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
|
||||||
// $ref: "#/responses/DocsSearchResponse"
|
// $ref: "#/responses/DocsSearchResponse"
|
||||||
// 500:
|
// 500:
|
||||||
// $ref: '#/responses/InternalError'
|
// $ref: '#/responses/InternalError'
|
||||||
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(compat.SearchImages)).Methods(http.MethodGet)
|
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(libpod.SearchImages)).Methods(http.MethodGet)
|
||||||
// swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
|
// swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
|
||||||
// ---
|
// ---
|
||||||
// tags:
|
// tags:
|
||||||
|
|
|
@ -2,7 +2,6 @@ package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -13,6 +12,7 @@ import (
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
"github.com/containers/libpod/pkg/api/handlers"
|
||||||
"github.com/containers/libpod/pkg/bindings"
|
"github.com/containers/libpod/pkg/bindings"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exists a lightweight way to determine if an image exists in local storage. It returns a
|
// Exists a lightweight way to determine if an image exists in local storage. It returns a
|
||||||
|
@ -308,3 +308,34 @@ func Push(ctx context.Context, source string, destination string, options entiti
|
||||||
_, err = conn.DoRequest(nil, http.MethodPost, path, params)
|
_, err = conn.DoRequest(nil, http.MethodPost, path, params)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search is the binding for libpod's v2 endpoints for Search images.
|
||||||
|
func Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
||||||
|
conn, err := bindings.GetClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params := url.Values{}
|
||||||
|
params.Set("term", term)
|
||||||
|
params.Set("limit", strconv.Itoa(opts.Limit))
|
||||||
|
for _, f := range opts.Filters {
|
||||||
|
params.Set("filters", f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.TLSVerify != types.OptionalBoolUndefined {
|
||||||
|
val := bool(opts.TLSVerify == types.OptionalBoolTrue)
|
||||||
|
params.Set("tlsVerify", strconv.FormatBool(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
results := []entities.ImageSearchReport{}
|
||||||
|
if err := response.Process(&results); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
package images
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod/image"
|
|
||||||
"github.com/containers/libpod/pkg/bindings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Search looks for the given image (term) in container image registries. The optional limit parameter sets
|
|
||||||
// a maximum number of results returned. The optional filters parameter allow for more specific image
|
|
||||||
// searches.
|
|
||||||
func Search(ctx context.Context, term string, limit *int, filters map[string][]string) ([]image.SearchResult, error) {
|
|
||||||
var (
|
|
||||||
searchResults []image.SearchResult
|
|
||||||
)
|
|
||||||
conn, err := bindings.GetClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params := url.Values{}
|
|
||||||
params.Set("term", term)
|
|
||||||
if limit != nil {
|
|
||||||
params.Set("limit", strconv.Itoa(*limit))
|
|
||||||
}
|
|
||||||
if filters != nil {
|
|
||||||
stringFilter, err := bindings.FiltersToString(filters)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params.Set("filters", stringFilter)
|
|
||||||
}
|
|
||||||
response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
|
|
||||||
if err != nil {
|
|
||||||
return searchResults, nil
|
|
||||||
}
|
|
||||||
return searchResults, response.Process(&searchResults)
|
|
||||||
}
|
|
|
@ -314,11 +314,11 @@ var _ = Describe("Podman images", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Search for an image", func() {
|
It("Search for an image", func() {
|
||||||
imgs, err := images.Search(bt.conn, "alpine", nil, nil)
|
reports, err := images.Search(bt.conn, "alpine", entities.ImageSearchOptions{})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(len(imgs)).To(BeNumerically(">", 1))
|
Expect(len(reports)).To(BeNumerically(">", 1))
|
||||||
var foundAlpine bool
|
var foundAlpine bool
|
||||||
for _, i := range imgs {
|
for _, i := range reports {
|
||||||
if i.Name == "docker.io/library/alpine" {
|
if i.Name == "docker.io/library/alpine" {
|
||||||
foundAlpine = true
|
foundAlpine = true
|
||||||
break
|
break
|
||||||
|
@ -327,23 +327,20 @@ var _ = Describe("Podman images", func() {
|
||||||
Expect(foundAlpine).To(BeTrue())
|
Expect(foundAlpine).To(BeTrue())
|
||||||
|
|
||||||
// Search for alpine with a limit of 10
|
// Search for alpine with a limit of 10
|
||||||
ten := 10
|
reports, err = images.Search(bt.conn, "docker.io/alpine", entities.ImageSearchOptions{Limit: 10})
|
||||||
imgs, err = images.Search(bt.conn, "docker.io/alpine", &ten, nil)
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(len(imgs)).To(BeNumerically("<=", 10))
|
Expect(len(reports)).To(BeNumerically("<=", 10))
|
||||||
|
|
||||||
// Search for alpine with stars greater than 100
|
// Search for alpine with stars greater than 100
|
||||||
filters := make(map[string][]string)
|
reports, err = images.Search(bt.conn, "docker.io/alpine", entities.ImageSearchOptions{Filters: []string{"stars=100"}})
|
||||||
filters["stars"] = []string{"100"}
|
|
||||||
imgs, err = images.Search(bt.conn, "docker.io/alpine", nil, filters)
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
for _, i := range imgs {
|
for _, i := range reports {
|
||||||
Expect(i.Stars).To(BeNumerically(">=", 100))
|
Expect(i.Stars).To(BeNumerically(">=", 100))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search with a fqdn
|
// Search with a fqdn
|
||||||
imgs, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", nil, nil)
|
reports, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", entities.ImageSearchOptions{})
|
||||||
Expect(len(imgs)).To(BeNumerically(">=", 1))
|
Expect(len(reports)).To(BeNumerically(">=", 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Prune images", func() {
|
It("Prune images", func() {
|
||||||
|
|
|
@ -19,4 +19,5 @@ type ImageEngine interface {
|
||||||
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
|
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
|
||||||
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
|
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
|
||||||
Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error
|
Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error
|
||||||
|
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,37 @@ type ImagePushOptions struct {
|
||||||
TLSVerify types.OptionalBool
|
TLSVerify types.OptionalBool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageSearchOptions are the arguments for searching images.
|
||||||
|
type ImageSearchOptions struct {
|
||||||
|
// Authfile is the path to the authentication file. Ignored for remote
|
||||||
|
// calls.
|
||||||
|
Authfile string
|
||||||
|
// Filters for the search results.
|
||||||
|
Filters []string
|
||||||
|
// Limit the number of results.
|
||||||
|
Limit int
|
||||||
|
// NoTrunc will not truncate the output.
|
||||||
|
NoTrunc bool
|
||||||
|
// TLSVerify to enable/disable HTTPS and certificate verification.
|
||||||
|
TLSVerify types.OptionalBool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageSearchReport is the response from searching images.
|
||||||
|
type ImageSearchReport struct {
|
||||||
|
// Index is the image index (e.g., "docker.io" or "quay.io")
|
||||||
|
Index string
|
||||||
|
// Name is the canoncical name of the image (e.g., "docker.io/library/alpine").
|
||||||
|
Name string
|
||||||
|
// Description of the image.
|
||||||
|
Description string
|
||||||
|
// Stars is the number of stars of the image.
|
||||||
|
Stars int
|
||||||
|
// Official indicates if it's an official image.
|
||||||
|
Official string
|
||||||
|
// Automated indicates if the image was created by an automated build.
|
||||||
|
Automated string
|
||||||
|
}
|
||||||
|
|
||||||
type ImageListOptions struct {
|
type ImageListOptions struct {
|
||||||
All bool `json:"all" schema:"all"`
|
All bool `json:"all" schema:"all"`
|
||||||
Filter []string `json:"Filter,omitempty"`
|
Filter []string `json:"Filter,omitempty"`
|
||||||
|
|
|
@ -425,3 +425,38 @@ func (ir *ImageEngine) Diff(_ context.Context, nameOrId string, _ entities.DiffO
|
||||||
}
|
}
|
||||||
return &entities.DiffReport{Changes: changes}, nil
|
return &entities.DiffReport{Changes: changes}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
||||||
|
filter, err := image.ParseSearchFilter(opts.Filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchOpts := image.SearchOptions{
|
||||||
|
Authfile: opts.Authfile,
|
||||||
|
Filter: *filter,
|
||||||
|
Limit: opts.Limit,
|
||||||
|
NoTrunc: opts.NoTrunc,
|
||||||
|
InsecureSkipTLSVerify: opts.TLSVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
searchResults, err := image.SearchImages(term, searchOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from image.SearchResults to entities.ImageSearchReport. We don't
|
||||||
|
// want to leak any low-level packages into the remote client, which
|
||||||
|
// requires converting.
|
||||||
|
reports := make([]entities.ImageSearchReport, len(searchResults))
|
||||||
|
for i := range searchResults {
|
||||||
|
reports[i].Index = searchResults[i].Index
|
||||||
|
reports[i].Name = searchResults[i].Name
|
||||||
|
reports[i].Description = searchResults[i].Index
|
||||||
|
reports[i].Stars = searchResults[i].Stars
|
||||||
|
reports[i].Official = searchResults[i].Official
|
||||||
|
reports[i].Automated = searchResults[i].Automated
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
|
@ -250,3 +250,7 @@ func (ir *ImageEngine) Diff(ctx context.Context, nameOrId string, _ entities.Dif
|
||||||
}
|
}
|
||||||
return &entities.DiffReport{Changes: changes}, nil
|
return &entities.DiffReport{Changes: changes}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
||||||
|
return images.Search(ir.ClientCxt, term, opts)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue