github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podmanV2/images/history.go (about) 1 package images 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 "text/tabwriter" 9 "text/template" 10 "time" 11 "unicode" 12 13 "github.com/containers/libpod/cmd/podmanV2/registry" 14 "github.com/containers/libpod/pkg/domain/entities" 15 "github.com/docker/go-units" 16 jsoniter "github.com/json-iterator/go" 17 "github.com/pkg/errors" 18 "github.com/spf13/cobra" 19 ) 20 21 var ( 22 long = `Displays the history of an image. 23 24 The information can be printed out in an easy to read, or user specified format, and can be truncated.` 25 26 // podman _history_ 27 historyCmd = &cobra.Command{ 28 Use: "history [flags] IMAGE", 29 Short: "Show history of a specified image", 30 Long: long, 31 Example: "podman history quay.io/fedora/fedora", 32 Args: cobra.ExactArgs(1), 33 PersistentPreRunE: preRunE, 34 RunE: history, 35 } 36 37 opts = struct { 38 human bool 39 noTrunc bool 40 quiet bool 41 format string 42 }{} 43 ) 44 45 func init() { 46 registry.Commands = append(registry.Commands, registry.CliCommand{ 47 Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, 48 Command: historyCmd, 49 }) 50 51 historyCmd.SetHelpTemplate(registry.HelpTemplate()) 52 historyCmd.SetUsageTemplate(registry.UsageTemplate()) 53 54 flags := historyCmd.Flags() 55 flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") 56 flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") 57 flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") 58 flags.BoolVar(&opts.noTrunc, "notruncate", false, "Do not truncate the output") 59 flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Display the numeric IDs only") 60 } 61 62 func history(cmd *cobra.Command, args []string) error { 63 results, err := registry.ImageEngine().History(context.Background(), args[0], entities.ImageHistoryOptions{}) 64 if err != nil { 65 return err 66 } 67 68 if opts.format == "json" { 69 var err error 70 if len(results.Layers) == 0 { 71 _, err = fmt.Fprintf(os.Stdout, "[]\n") 72 } else { 73 // ah-hoc change to "Created": type and format 74 type layer struct { 75 entities.ImageHistoryLayer 76 Created string `json:"Created"` 77 } 78 79 layers := make([]layer, len(results.Layers)) 80 for i, l := range results.Layers { 81 layers[i].ImageHistoryLayer = l 82 layers[i].Created = l.Created.Format(time.RFC3339) 83 } 84 json := jsoniter.ConfigCompatibleWithStandardLibrary 85 enc := json.NewEncoder(os.Stdout) 86 err = enc.Encode(layers) 87 } 88 return err 89 } 90 var hr []historyreporter 91 for _, l := range results.Layers { 92 hr = append(hr, historyreporter{l}) 93 } 94 // Defaults 95 hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n" 96 row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" 97 98 if len(opts.format) > 0 { 99 hdr = "" 100 row = opts.format 101 if !strings.HasSuffix(opts.format, "\n") { 102 row += "\n" 103 } 104 } else { 105 switch { 106 case opts.human: 107 row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" 108 case opts.noTrunc: 109 row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n" 110 case opts.quiet: 111 hdr = "" 112 row = "{{.ID}}\n" 113 } 114 } 115 format := hdr + "{{range . }}" + row + "{{end}}" 116 117 tmpl := template.Must(template.New("report").Parse(format)) 118 w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) 119 err = tmpl.Execute(w, hr) 120 if err != nil { 121 fmt.Fprintln(os.Stderr, errors.Wrapf(err, "Failed to print report")) 122 } 123 w.Flush() 124 return nil 125 } 126 127 type historyreporter struct { 128 entities.ImageHistoryLayer 129 } 130 131 func (h historyreporter) Created() string { 132 if opts.human { 133 return units.HumanDuration(time.Since(h.ImageHistoryLayer.Created)) + " ago" 134 } 135 return h.ImageHistoryLayer.Created.Format(time.RFC3339) 136 } 137 138 func (h historyreporter) Size() string { 139 s := units.HumanSizeWithPrecision(float64(h.ImageHistoryLayer.Size), 3) 140 i := strings.LastIndexFunc(s, unicode.IsNumber) 141 return s[:i+1] + " " + s[i+1:] 142 } 143 144 func (h historyreporter) CreatedBy() string { 145 if len(h.ImageHistoryLayer.CreatedBy) > 45 { 146 return h.ImageHistoryLayer.CreatedBy[:45-3] + "..." 147 } 148 return h.ImageHistoryLayer.CreatedBy 149 } 150 151 func (h historyreporter) ID() string { 152 if !opts.noTrunc && len(h.ImageHistoryLayer.ID) >= 12 { 153 return h.ImageHistoryLayer.ID[0:12] 154 } 155 return h.ImageHistoryLayer.ID 156 }