Files
podman/cmd/podman/search.go
Daniel J Walsh 0830bb9035 Capatilize all usage and descriptions
We have no consistancy in out option usages and descritions
on whether or not the first letter should be capatalized.

This patch forces them all to be capatilized.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2019-02-05 10:42:04 -08:00

331 lines
9.1 KiB
Go

package main
import (
"context"
"reflect"
"strconv"
"strings"
"github.com/containers/image/docker"
"github.com/containers/image/types"
"github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod/common"
sysreg "github.com/containers/libpod/pkg/registries"
"github.com/docker/distribution/reference"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
descriptionTruncLength = 44
maxQueries = 25
)
var (
searchFlags = []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ",
},
cli.StringSliceFlag{
Name: "filter, f",
Usage: "Filter output based on conditions provided (default [])",
},
cli.StringFlag{
Name: "format",
Usage: "Change the output format to a Go template",
},
cli.IntFlag{
Name: "limit",
Usage: "Limit the number of results",
},
cli.BoolFlag{
Name: "no-trunc",
Usage: "Do not truncate the output",
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "Require HTTPS and verify certificates when contacting registries (default: true)",
},
}
searchDescription = `
Search registries for a given image. Can search all the default registries or a specific registry.
Can limit the number of results, and filter the output based on certain conditions.`
searchCommand = cli.Command{
Name: "search",
Usage: "Search registry for image",
Description: searchDescription,
Flags: sortFlags(searchFlags),
Action: searchCmd,
ArgsUsage: "TERM",
OnUsageError: usageErrorHandler,
}
)
type searchParams struct {
Index string
Name string
Description string
Stars int
Official string
Automated string
}
type searchOpts struct {
filter []string
limit int
noTrunc bool
format string
authfile string
insecureSkipTLSVerify types.OptionalBool
}
type searchFilterParams struct {
stars int
isAutomated *bool
isOfficial *bool
}
func searchCmd(c *cli.Context) error {
args := c.Args()
if len(args) > 1 {
return errors.Errorf("too many arguments. Requires exactly 1")
}
if len(args) == 0 {
return errors.Errorf("no argument given, requires exactly 1 argument")
}
term := args[0]
// Check if search term has a registry in it
registry, err := getRegistry(term)
if err != nil {
return errors.Wrapf(err, "error getting registry from %q", term)
}
if registry != "" {
term = term[len(registry)+1:]
}
if err := validateFlags(c, searchFlags); err != nil {
return err
}
format := genSearchFormat(c.String("format"))
opts := searchOpts{
format: format,
noTrunc: c.Bool("no-trunc"),
limit: c.Int("limit"),
filter: c.StringSlice("filter"),
authfile: getAuthFile(c.String("authfile")),
}
if c.IsSet("tls-verify") {
opts.insecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify"))
}
registries, err := getRegistries(registry)
if err != nil {
return err
}
filter, err := parseSearchFilter(&opts)
if err != nil {
return err
}
return generateSearchOutput(term, registries, opts, *filter)
}
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 []searchParams) (genericParams []interface{}) {
for _, v := range params {
genericParams = append(genericParams, interface{}(v))
}
return genericParams
}
func (s *searchParams) headerMap() map[string]string {
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(splitCamelCase(value))
}
return values
}
// getRegistries returns the list of registries to search, depending on an optional registry specification
func getRegistries(registry string) ([]string, error) {
var registries []string
if registry != "" {
registries = append(registries, registry)
} else {
var err error
registries, err = sysreg.GetRegistries()
if err != nil {
return nil, errors.Wrapf(err, "error getting registries to search")
}
}
return registries, nil
}
func getSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) ([]searchParams, error) {
// Max number of queries by default is 25
limit := maxQueries
if opts.limit != 0 {
limit = opts.limit
}
sc := common.GetSystemContext("", opts.authfile, false)
sc.DockerInsecureSkipTLSVerify = opts.insecureSkipTLSVerify
sc.SystemRegistriesConfPath = sysreg.SystemRegistriesConfPath() // FIXME: Set this more globally. Probably no reason not to have it in every types.SystemContext, and to compute the value just once in one place.
var paramsArr []searchParams
for _, reg := range registries {
results, err := docker.SearchRegistry(context.TODO(), sc, reg, term, limit)
if err != nil {
logrus.Errorf("error searching registry %q: %v", reg, err)
continue
}
index := reg
arr := strings.Split(reg, ".")
if len(arr) > 2 {
index = strings.Join(arr[len(arr)-2:], ".")
}
// limit is the number of results to output
// if the total number of results is less than the limit, output all
// if the limit has been set by the user, output those number of queries
limit := maxQueries
if len(results) < limit {
limit = len(results)
}
if opts.limit != 0 && opts.limit < len(results) {
limit = opts.limit
}
for i := 0; i < limit; i++ {
if len(opts.filter) > 0 {
// Check whether query matches filters
if !(matchesAutomatedFilter(filter, results[i]) && matchesOfficialFilter(filter, results[i]) && matchesStarFilter(filter, results[i])) {
continue
}
}
official := ""
if results[i].IsOfficial {
official = "[OK]"
}
automated := ""
if results[i].IsAutomated {
automated = "[OK]"
}
description := strings.Replace(results[i].Description, "\n", " ", -1)
if len(description) > 44 && !opts.noTrunc {
description = description[:descriptionTruncLength] + "..."
}
name := reg + "/" + results[i].Name
if index == "docker.io" && !strings.Contains(results[i].Name, "/") {
name = index + "/library/" + results[i].Name
}
params := searchParams{
Index: index,
Name: name,
Description: description,
Official: official,
Automated: automated,
Stars: results[i].StarCount,
}
paramsArr = append(paramsArr, params)
}
}
return paramsArr, nil
}
func generateSearchOutput(term string, registries []string, opts searchOpts, filter searchFilterParams) error {
searchOutput, err := getSearchOutput(term, registries, opts, filter)
if err != nil {
return err
}
if len(searchOutput) == 0 {
return nil
}
out := formats.StdoutTemplateArray{Output: searchToGeneric(searchOutput), Template: opts.format, Fields: searchOutput[0].headerMap()}
return formats.Writer(out).Out()
}
func parseSearchFilter(opts *searchOpts) (*searchFilterParams, error) {
filterParams := &searchFilterParams{}
ptrTrue := true
ptrFalse := false
for _, filter := range opts.filter {
arr := strings.Split(filter, "=")
switch arr[0] {
case "stars":
if len(arr) < 2 {
return nil, errors.Errorf("invalid `stars` filter %q, should be stars=<value>", filter)
}
stars, err := strconv.Atoi(arr[1])
if err != nil {
return nil, errors.Wrapf(err, "incorrect value type for stars filter")
}
filterParams.stars = stars
break
case "is-automated":
if len(arr) == 2 && arr[1] == "false" {
filterParams.isAutomated = &ptrFalse
} else {
filterParams.isAutomated = &ptrTrue
}
break
case "is-official":
if len(arr) == 2 && arr[1] == "false" {
filterParams.isOfficial = &ptrFalse
} else {
filterParams.isOfficial = &ptrTrue
}
break
default:
return nil, errors.Errorf("invalid filter type %q", filter)
}
}
return filterParams, nil
}
func matchesStarFilter(filter searchFilterParams, result docker.SearchResult) bool {
return result.StarCount >= filter.stars
}
func matchesAutomatedFilter(filter searchFilterParams, result docker.SearchResult) bool {
if filter.isAutomated != nil {
return result.IsAutomated == *filter.isAutomated
}
return true
}
func matchesOfficialFilter(filter searchFilterParams, result docker.SearchResult) bool {
if filter.isOfficial != nil {
return result.IsOfficial == *filter.isOfficial
}
return true
}
func getRegistry(image string) (string, error) {
// It is possible to only have the registry name in the format "myregistry/"
// if so, just trim the "/" from the end and return the registry name
if strings.HasSuffix(image, "/") {
return strings.TrimSuffix(image, "/"), nil
}
imgRef, err := reference.Parse(image)
if err != nil {
return "", err
}
return reference.Domain(imgRef.(reference.Named)), nil
}