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  }