github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/history.go (about) 1 package main 2 3 import ( 4 "reflect" 5 "strconv" 6 "strings" 7 "time" 8 9 "github.com/containers/buildah/pkg/formats" 10 "github.com/containers/libpod/cmd/podman/cliconfig" 11 "github.com/containers/libpod/libpod/image" 12 "github.com/containers/libpod/pkg/adapter" 13 "github.com/docker/go-units" 14 "github.com/pkg/errors" 15 "github.com/spf13/cobra" 16 ) 17 18 const createdByTruncLength = 45 19 20 // historyTemplateParams stores info about each layer 21 type historyTemplateParams struct { 22 ID string 23 Created string 24 CreatedBy string 25 Size string 26 Comment string 27 } 28 29 // historyOptions stores cli flag values 30 type historyOptions struct { 31 human bool 32 noTrunc bool 33 quiet bool 34 format string 35 } 36 37 var ( 38 historyCommand cliconfig.HistoryValues 39 40 historyDescription = `Displays the history of an image. 41 42 The information can be printed out in an easy to read, or user specified format, and can be truncated.` 43 _historyCommand = &cobra.Command{ 44 Use: "history [flags] IMAGE", 45 Short: "Show history of a specified image", 46 Long: historyDescription, 47 RunE: func(cmd *cobra.Command, args []string) error { 48 historyCommand.InputArgs = args 49 historyCommand.GlobalFlags = MainGlobalOpts 50 historyCommand.Remote = remoteclient 51 return historyCmd(&historyCommand) 52 }, 53 } 54 ) 55 56 func init() { 57 historyCommand.Command = _historyCommand 58 historyCommand.SetHelpTemplate(HelpTemplate()) 59 historyCommand.SetUsageTemplate(UsageTemplate()) 60 flags := historyCommand.Flags() 61 flags.StringVar(&historyCommand.Format, "format", "", "Change the output to JSON or a Go template") 62 flags.BoolVarP(&historyCommand.Human, "human", "H", true, "Display sizes and dates in human readable format") 63 // notrucate needs to be added 64 flags.BoolVar(&historyCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") 65 flags.BoolVarP(&historyCommand.Quiet, "quiet", "q", false, "Display the numeric IDs only") 66 67 } 68 69 func historyCmd(c *cliconfig.HistoryValues) error { 70 runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) 71 if err != nil { 72 return errors.Wrapf(err, "could not get runtime") 73 } 74 defer runtime.DeferredShutdown(false) 75 76 format := genHistoryFormat(c.Format, c.Quiet) 77 78 args := c.InputArgs 79 if len(args) == 0 { 80 return errors.Errorf("an image name must be specified") 81 } 82 if len(args) > 1 { 83 return errors.Errorf("podman history takes at most 1 argument") 84 } 85 86 image, err := runtime.NewImageFromLocal(args[0]) 87 if err != nil { 88 return err 89 } 90 opts := historyOptions{ 91 human: c.Human, 92 noTrunc: c.NoTrunc, 93 quiet: c.Quiet, 94 format: format, 95 } 96 97 history, err := image.History(getContext()) 98 if err != nil { 99 return errors.Wrapf(err, "error getting history of image %q", image.InputName) 100 } 101 102 return generateHistoryOutput(history, opts) 103 } 104 105 func genHistoryFormat(format string, quiet bool) string { 106 if format != "" { 107 // "\t" from the command line is not being recognized as a tab 108 // replacing the string "\t" to a tab character if the user passes in "\t" 109 return strings.Replace(format, `\t`, "\t", -1) 110 } 111 if quiet { 112 return formats.IDString 113 } 114 return "table {{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\t" 115 } 116 117 // historyToGeneric makes an empty array of interfaces for output 118 func historyToGeneric(templParams []historyTemplateParams, jsonParams []*image.History) (genericParams []interface{}) { 119 if len(templParams) > 0 { 120 for _, v := range templParams { 121 genericParams = append(genericParams, interface{}(v)) 122 } 123 return 124 } 125 for _, v := range jsonParams { 126 genericParams = append(genericParams, interface{}(v)) 127 } 128 return 129 } 130 131 // generate the header based on the template provided 132 func (h *historyTemplateParams) headerMap() map[string]string { 133 v := reflect.Indirect(reflect.ValueOf(h)) 134 values := make(map[string]string) 135 for h := 0; h < v.NumField(); h++ { 136 key := v.Type().Field(h).Name 137 value := key 138 values[key] = strings.ToUpper(splitCamelCase(value)) 139 } 140 return values 141 } 142 143 // getHistorytemplateOutput gets the modified history information to be printed in human readable format 144 func getHistoryTemplateOutput(history []*image.History, opts historyOptions) []historyTemplateParams { 145 var ( 146 outputSize string 147 createdTime string 148 createdBy string 149 historyOutput []historyTemplateParams 150 ) 151 for _, hist := range history { 152 imageID := hist.ID 153 if !opts.noTrunc && imageID != "<missing>" { 154 imageID = shortID(imageID) 155 } 156 157 if opts.human { 158 createdTime = units.HumanDuration(time.Since(*hist.Created)) + " ago" 159 outputSize = units.HumanSize(float64(hist.Size)) 160 } else { 161 createdTime = (hist.Created).Format(time.RFC3339) 162 outputSize = strconv.FormatInt(hist.Size, 10) 163 } 164 165 createdBy = strings.Join(strings.Fields(hist.CreatedBy), " ") 166 if !opts.noTrunc && len(createdBy) > createdByTruncLength { 167 createdBy = createdBy[:createdByTruncLength-3] + "..." 168 } 169 170 params := historyTemplateParams{ 171 ID: imageID, 172 Created: createdTime, 173 CreatedBy: createdBy, 174 Size: outputSize, 175 Comment: hist.Comment, 176 } 177 historyOutput = append(historyOutput, params) 178 } 179 return historyOutput 180 } 181 182 // generateHistoryOutput generates the history based on the format given 183 func generateHistoryOutput(history []*image.History, opts historyOptions) error { 184 if len(history) == 0 { 185 return nil 186 } 187 188 var out formats.Writer 189 190 switch opts.format { 191 case formats.JSONString: 192 out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, history)} 193 default: 194 historyOutput := getHistoryTemplateOutput(history, opts) 195 out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []*image.History{}), Template: opts.format, Fields: historyOutput[0].headerMap()} 196 } 197 198 return out.Out() 199 }