mirror of
https://github.com/containers/podman.git
synced 2025-05-22 01:27:07 +08:00
199 lines
5.5 KiB
Go
199 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/buildah/pkg/formats"
|
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
|
"github.com/containers/libpod/libpod/image"
|
|
"github.com/containers/libpod/pkg/adapter"
|
|
"github.com/docker/go-units"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
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 (
|
|
historyCommand cliconfig.HistoryValues
|
|
|
|
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 = &cobra.Command{
|
|
Use: "history [flags] IMAGE",
|
|
Short: "Show history of a specified image",
|
|
Long: historyDescription,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
historyCommand.InputArgs = args
|
|
historyCommand.GlobalFlags = MainGlobalOpts
|
|
historyCommand.Remote = remoteclient
|
|
return historyCmd(&historyCommand)
|
|
},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
historyCommand.Command = _historyCommand
|
|
historyCommand.SetHelpTemplate(HelpTemplate())
|
|
historyCommand.SetUsageTemplate(UsageTemplate())
|
|
flags := historyCommand.Flags()
|
|
flags.StringVar(&historyCommand.Format, "format", "", "Change the output to JSON or a Go template")
|
|
flags.BoolVarP(&historyCommand.Human, "human", "H", true, "Display sizes and dates in human readable format")
|
|
// notrucate needs to be added
|
|
flags.BoolVar(&historyCommand.NoTrunc, "no-trunc", false, "Do not truncate the output")
|
|
flags.BoolVarP(&historyCommand.Quiet, "quiet", "q", false, "Display the numeric IDs only")
|
|
|
|
}
|
|
|
|
func historyCmd(c *cliconfig.HistoryValues) error {
|
|
runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "could not get runtime")
|
|
}
|
|
defer runtime.DeferredShutdown(false)
|
|
|
|
format := genHistoryFormat(c.Format, c.Quiet)
|
|
|
|
args := c.InputArgs
|
|
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.Human,
|
|
noTrunc: c.NoTrunc,
|
|
quiet: c.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()
|
|
}
|