diff options
Diffstat (limited to 'cmd/podmanV2/images')
-rw-r--r-- | cmd/podmanV2/images/history.go | 55 | ||||
-rw-r--r-- | cmd/podmanV2/images/inspect.go | 3 | ||||
-rw-r--r-- | cmd/podmanV2/images/list.go | 64 | ||||
-rw-r--r-- | cmd/podmanV2/images/load.go | 42 | ||||
-rw-r--r-- | cmd/podmanV2/images/rm.go | 8 | ||||
-rw-r--r-- | cmd/podmanV2/images/rmi.go | 1 | ||||
-rw-r--r-- | cmd/podmanV2/images/search.go | 157 | ||||
-rw-r--r-- | cmd/podmanV2/images/tag.go | 11 |
8 files changed, 300 insertions, 41 deletions
diff --git a/cmd/podmanV2/images/history.go b/cmd/podmanV2/images/history.go index f6f15e2f2..e3bb7a051 100644 --- a/cmd/podmanV2/images/history.go +++ b/cmd/podmanV2/images/history.go @@ -8,10 +8,11 @@ import ( "text/tabwriter" "text/template" "time" + "unicode" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -52,7 +53,7 @@ func init() { flags := historyCmd.Flags() flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") - flags.BoolVarP(&opts.human, "human", "H", false, "Display sizes and dates in human readable format") + flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") flags.BoolVar(&opts.noTrunc, "notruncate", false, "Do not truncate the output") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Display the numeric IDs only") @@ -78,7 +79,7 @@ func history(cmd *cobra.Command, args []string) error { layers := make([]layer, len(results.Layers)) for i, l := range results.Layers { layers[i].ImageHistoryLayer = l - layers[i].Created = time.Unix(l.Created, 0).Format(time.RFC3339) + layers[i].Created = l.Created.Format(time.RFC3339) } json := jsoniter.ConfigCompatibleWithStandardLibrary enc := json.NewEncoder(os.Stdout) @@ -86,10 +87,13 @@ func history(cmd *cobra.Command, args []string) error { } return err } - + var hr []historyreporter + for _, l := range results.Layers { + hr = append(hr, historyreporter{l}) + } // Defaults hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" - row := "{{slice .ID 0 12}}\t{{humanDuration .Created}}\t{{ellipsis .CreatedBy 45}}\t{{.Size}}\t{{.Comment}}\n" + row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" if len(opts.format) > 0 { hdr = "" @@ -100,9 +104,9 @@ func history(cmd *cobra.Command, args []string) error { } else { switch { case opts.human: - row = "{{slice .ID 0 12}}\t{{humanDuration .Created}}\t{{ellipsis .CreatedBy 45}}\t{{humanSize .Size}}\t{{.Comment}}\n" + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" case opts.noTrunc: - row = "{{.ID}}\t{{humanDuration .Created}}\t{{.CreatedBy}}\t{{humanSize .Size}}\t{{.Comment}}\n" + row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" case opts.quiet: hdr = "" row = "{{.ID}}\n" @@ -110,14 +114,43 @@ func history(cmd *cobra.Command, args []string) error { } format := hdr + "{{range . }}" + row + "{{end}}" - tmpl := template.Must(template.New("report").Funcs(report.PodmanTemplateFuncs()).Parse(format)) + tmpl := template.Must(template.New("report").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - - _, _ = w.Write(report.ReportHeader("id", "created", "created by", "size", "comment")) - err = tmpl.Execute(w, results.Layers) + err = tmpl.Execute(w, hr) if err != nil { fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Failed to print report")) } w.Flush() return nil } + +type historyreporter struct { + entities.ImageHistoryLayer +} + +func (h historyreporter) Created() string { + if opts.human { + return units.HumanDuration(time.Since(h.ImageHistoryLayer.Created)) + " ago" + } + return h.ImageHistoryLayer.Created.Format(time.RFC3339) +} + +func (h historyreporter) Size() string { + s := units.HumanSizeWithPrecision(float64(h.ImageHistoryLayer.Size), 3) + i := strings.LastIndexFunc(s, unicode.IsNumber) + return s[:i+1] + " " + s[i+1:] +} + +func (h historyreporter) CreatedBy() string { + if len(h.ImageHistoryLayer.CreatedBy) > 45 { + return h.ImageHistoryLayer.CreatedBy[:45-3] + "..." + } + return h.ImageHistoryLayer.CreatedBy +} + +func (h historyreporter) ID() string { + if !opts.noTrunc && len(h.ImageHistoryLayer.ID) >= 12 { + return h.ImageHistoryLayer.ID[0:12] + } + return h.ImageHistoryLayer.ID +} diff --git a/cmd/podmanV2/images/inspect.go b/cmd/podmanV2/images/inspect.go index d7f6b0ee1..2ee2d86ee 100644 --- a/cmd/podmanV2/images/inspect.go +++ b/cmd/podmanV2/images/inspect.go @@ -67,7 +67,6 @@ func inspect(cmd *cobra.Command, args []string) error { } return nil } - row := inspectFormat(inspectOpts.Format) format := "{{range . }}" + row + "{{end}}" tmpl, err := template.New("inspect").Parse(format) @@ -77,7 +76,7 @@ func inspect(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer func() { _ = w.Flush() }() - err = tmpl.Execute(w, results) + err = tmpl.Execute(w, results.Images) if err != nil { return err } diff --git a/cmd/podmanV2/images/list.go b/cmd/podmanV2/images/list.go index 2d6cb3596..d594a4323 100644 --- a/cmd/podmanV2/images/list.go +++ b/cmd/podmanV2/images/list.go @@ -9,10 +9,11 @@ import ( "text/tabwriter" "text/template" "time" + "unicode" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" jsoniter "github.com/json-iterator/go" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -130,16 +131,13 @@ func writeJSON(imageS []*entities.ImageSummary) error { } func writeTemplate(imageS []*entities.ImageSummary, err error) error { - type image struct { - entities.ImageSummary - Repository string `json:"repository,omitempty"` - Tag string `json:"tag,omitempty"` - } - - imgs := make([]image, 0, len(imageS)) + var ( + hdr, row string + ) + imgs := make([]imageReporter, 0, len(imageS)) for _, e := range imageS { for _, tag := range e.RepoTags { - var h image + var h imageReporter h.ImageSummary = *e h.Repository, h.Tag = tokenRepoTag(tag) imgs = append(imgs, h) @@ -148,11 +146,16 @@ func writeTemplate(imageS []*entities.ImageSummary, err error) error { listFlag.readOnly = true } } - - hdr, row := imageListFormat(listFlag) + if len(listFlag.format) < 1 { + hdr, row = imageListFormat(listFlag) + } else { + row = listFlag.format + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + } format := hdr + "{{range . }}" + row + "{{end}}" - - tmpl := template.Must(template.New("list").Funcs(report.PodmanTemplateFuncs()).Parse(format)) + tmpl := template.Must(template.New("list").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() return tmpl.Execute(w, imgs) @@ -200,7 +203,7 @@ func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool { func imageListFormat(flags listFlagType) (string, string) { if flags.quiet { - return "", "{{slice .ID 0 12}}\n" + return "", "{{.ID}}\n" } // Defaults @@ -216,15 +219,15 @@ func imageListFormat(flags listFlagType) (string, string) { if flags.noTrunc { row += "\tsha256:{{.ID}}" } else { - row += "\t{{slice .ID 0 12}}" + row += "\t{{.ID}}" } hdr += "\tCREATED\tSIZE" - row += "\t{{humanDuration .Created}}\t{{humanSize .Size}}" + row += "\t{{.Created}}\t{{.Size}}" if flags.history { hdr += "\tHISTORY" - row += "\t{{if .History}}{{join .History \", \"}}{{else}}<none>{{end}}" + row += "\t{{if .History}}{{.History}}{{else}}<none>{{end}}" } if flags.readOnly { @@ -241,3 +244,30 @@ func imageListFormat(flags listFlagType) (string, string) { row += "\n" return hdr, row } + +type imageReporter struct { + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` + entities.ImageSummary +} + +func (i imageReporter) ID() string { + if !listFlag.noTrunc && len(i.ImageSummary.ID) >= 12 { + return i.ImageSummary.ID[0:12] + } + return i.ImageSummary.ID +} + +func (i imageReporter) Created() string { + return units.HumanDuration(time.Since(time.Unix(i.ImageSummary.Created, 0))) + " ago" +} + +func (i imageReporter) Size() string { + s := units.HumanSizeWithPrecision(float64(i.ImageSummary.Size), 3) + j := strings.LastIndexFunc(s, unicode.IsNumber) + return s[:j+1] + " " + s[j+1:] +} + +func (i imageReporter) History() string { + return strings.Join(i.ImageSummary.History, ", ") +} diff --git a/cmd/podmanV2/images/load.go b/cmd/podmanV2/images/load.go index f60dc4908..315dbed3a 100644 --- a/cmd/podmanV2/images/load.go +++ b/cmd/podmanV2/images/load.go @@ -3,11 +3,18 @@ package images import ( "context" "fmt" + "io" + "io/ioutil" + "os" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/libpod/cmd/podmanV2/parse" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" ) var ( @@ -46,11 +53,40 @@ func init() { func load(cmd *cobra.Command, args []string) error { if len(args) > 0 { - repo, err := image.NormalizedTag(args[0]) + ref, err := reference.Parse(args[0]) if err != nil { return err } - loadOpts.Name = repo.Name() + if t, ok := ref.(reference.Tagged); ok { + loadOpts.Tag = t.Tag() + } else { + loadOpts.Tag = "latest" + } + if r, ok := ref.(reference.Named); ok { + fmt.Println(r.Name()) + loadOpts.Name = r.Name() + } + } + if len(loadOpts.Input) > 0 { + if err := parse.ValidateFileName(loadOpts.Input); err != nil { + return err + } + } else { + if terminal.IsTerminal(int(os.Stdin.Fd())) { + return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.") + } + outFile, err := ioutil.TempFile(util.Tmpdir(), "podman") + if err != nil { + return errors.Errorf("error creating file %v", err) + } + defer os.Remove(outFile.Name()) + defer outFile.Close() + + _, err = io.Copy(outFile, os.Stdin) + if err != nil { + return errors.Errorf("error copying file %v", err) + } + loadOpts.Input = outFile.Name() } response, err := registry.ImageEngine().Load(context.Background(), loadOpts) if err != nil { diff --git a/cmd/podmanV2/images/rm.go b/cmd/podmanV2/images/rm.go index bb5880de3..6784182d9 100644 --- a/cmd/podmanV2/images/rm.go +++ b/cmd/podmanV2/images/rm.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -33,11 +34,13 @@ func init() { Parent: imageCmd, }) - flags := rmCmd.Flags() + imageRemoveFlagSet(rmCmd.Flags()) +} + +func imageRemoveFlagSet(flags *pflag.FlagSet) { flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images") flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image") } - func rm(cmd *cobra.Command, args []string) error { if len(args) < 1 && !imageOpts.All { @@ -46,7 +49,6 @@ func rm(cmd *cobra.Command, args []string) error { if len(args) > 0 && imageOpts.All { return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") } - report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts) if err != nil { switch { diff --git a/cmd/podmanV2/images/rmi.go b/cmd/podmanV2/images/rmi.go index 7f9297bc9..973763966 100644 --- a/cmd/podmanV2/images/rmi.go +++ b/cmd/podmanV2/images/rmi.go @@ -27,4 +27,5 @@ func init() { }) rmiCmd.SetHelpTemplate(registry.HelpTemplate()) rmiCmd.SetUsageTemplate(registry.UsageTemplate()) + imageRemoveFlagSet(rmiCmd.Flags()) } diff --git a/cmd/podmanV2/images/search.go b/cmd/podmanV2/images/search.go new file mode 100644 index 000000000..2ab9735ec --- /dev/null +++ b/cmd/podmanV2/images/search.go @@ -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 +} diff --git a/cmd/podmanV2/images/tag.go b/cmd/podmanV2/images/tag.go index f66fe7857..f8799d4a7 100644 --- a/cmd/podmanV2/images/tag.go +++ b/cmd/podmanV2/images/tag.go @@ -9,11 +9,12 @@ import ( var ( tagDescription = "Adds one or more additional names to locally-stored image." tagCommand = &cobra.Command{ - Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", - Short: "Add an additional name to a local image", - Long: tagDescription, - RunE: tag, - Args: cobra.MinimumNArgs(2), + Use: "tag [flags] IMAGE TARGET_NAME [TARGET_NAME...]", + Short: "Add an additional name to a local image", + Long: tagDescription, + RunE: tag, + PreRunE: preRunE, + Args: cobra.MinimumNArgs(2), Example: `podman tag 0e3bbc2 fedora:latest podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, |