Files
podman/cmd/podman/history.go
baude e68f03ae45 Embed runtime struct in super localRuntime
We clean up the code by eliminating stuttering references when we embed
the runtime struct into localRuntime.  Makes for less change in the future
as well.

++ jhonce

Signed-off-by: baude <bbaude@redhat.com>
2019-01-15 16:01:25 -06:00

204 lines
5.3 KiB
Go

package main
import (
"reflect"
"strconv"
"strings"
"time"
"github.com/containers/libpod/cmd/podman/formats"
"github.com/containers/libpod/libpod/adapter"
"github.com/containers/libpod/libpod/image"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
const createdByTruncLength = 45
// historyTemplateParams stores info about each layer
type historyTemplateParams struct {
ID string
Created string
CreatedBy string
Size string
Comment string
}
// historyOptions stores cli flag values
type historyOptions struct {
human bool
noTrunc bool
quiet bool
format string
}
var (
historyFlags = []cli.Flag{
cli.BoolTFlag{
Name: "human, H",
Usage: "Display sizes and dates in human readable format",
},
cli.BoolFlag{
Name: "no-trunc, notruncate",
Usage: "Do not truncate the output",
},
cli.BoolFlag{
Name: "quiet, q",
Usage: "Display the numeric IDs only",
},
cli.StringFlag{
Name: "format",
Usage: "Change the output to JSON or a Go template",
},
}
historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " +
"or user specified format, and can be truncated."
historyCommand = cli.Command{
Name: "history",
Usage: "Show history of a specified image",
Description: historyDescription,
Flags: sortFlags(historyFlags),
Action: historyCmd,
ArgsUsage: "",
UseShortOptionHandling: true,
OnUsageError: usageErrorHandler,
}
)
func historyCmd(c *cli.Context) error {
if err := validateFlags(c, historyFlags); err != nil {
return err
}
runtime, err := adapter.GetRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
defer runtime.Shutdown(false)
format := genHistoryFormat(c.String("format"), c.Bool("quiet"))
args := c.Args()
if len(args) == 0 {
return errors.Errorf("an image name must be specified")
}
if len(args) > 1 {
return errors.Errorf("podman history takes at most 1 argument")
}
image, err := runtime.NewImageFromLocal(args[0])
if err != nil {
return err
}
opts := historyOptions{
human: c.BoolT("human"),
noTrunc: c.Bool("no-trunc"),
quiet: c.Bool("quiet"),
format: format,
}
history, err := image.History(getContext())
if err != nil {
return errors.Wrapf(err, "error getting history of image %q", image.InputName)
}
return generateHistoryOutput(history, opts)
}
func genHistoryFormat(format string, quiet bool) 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)
}
if quiet {
return formats.IDString
}
return "table {{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\t"
}
// historyToGeneric makes an empty array of interfaces for output
func historyToGeneric(templParams []historyTemplateParams, JSONParams []*image.History) (genericParams []interface{}) {
if len(templParams) > 0 {
for _, v := range templParams {
genericParams = append(genericParams, interface{}(v))
}
return
}
for _, v := range JSONParams {
genericParams = append(genericParams, interface{}(v))
}
return
}
// generate the header based on the template provided
func (h *historyTemplateParams) headerMap() map[string]string {
v := reflect.Indirect(reflect.ValueOf(h))
values := make(map[string]string)
for h := 0; h < v.NumField(); h++ {
key := v.Type().Field(h).Name
value := key
values[key] = strings.ToUpper(splitCamelCase(value))
}
return values
}
// getHistorytemplateOutput gets the modified history information to be printed in human readable format
func getHistoryTemplateOutput(history []*image.History, opts historyOptions) (historyOutput []historyTemplateParams) {
var (
outputSize string
createdTime string
createdBy string
)
for _, hist := range history {
imageID := hist.ID
if !opts.noTrunc && imageID != "<missing>" {
imageID = shortID(imageID)
}
if opts.human {
createdTime = units.HumanDuration(time.Since((*hist.Created))) + " ago"
outputSize = units.HumanSize(float64(hist.Size))
} else {
createdTime = (hist.Created).Format(time.RFC3339)
outputSize = strconv.FormatInt(hist.Size, 10)
}
createdBy = strings.Join(strings.Fields(hist.CreatedBy), " ")
if !opts.noTrunc && len(createdBy) > createdByTruncLength {
createdBy = createdBy[:createdByTruncLength-3] + "..."
}
params := historyTemplateParams{
ID: imageID,
Created: createdTime,
CreatedBy: createdBy,
Size: outputSize,
Comment: hist.Comment,
}
historyOutput = append(historyOutput, params)
}
return
}
// generateHistoryOutput generates the history based on the format given
func generateHistoryOutput(history []*image.History, opts historyOptions) error {
if len(history) == 0 {
return nil
}
var out formats.Writer
switch opts.format {
case formats.JSONString:
out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, history)}
default:
historyOutput := getHistoryTemplateOutput(history, opts)
out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []*image.History{}), Template: opts.format, Fields: historyOutput[0].headerMap()}
}
return formats.Writer(out).Out()
}