From 088d5af87984e8e8348f3b70004d8107df645f73 Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Thu, 21 Jun 2018 16:11:59 -0400 Subject: Podman history now prints out intermediate image IDs If the intermediate image exists in the store, podman history will show the IDs of the intermediate image of each layer. Signed-off-by: umohnani8 Closes: #982 Approved by: mheon --- cmd/podman/history.go | 81 +++++++++++------------------------------------- libpod/image/image.go | 78 +++++++++++++++++++++++++++++++++++++++++++--- pkg/varlinkapi/images.go | 26 ++++++---------- 3 files changed, 101 insertions(+), 84 deletions(-) diff --git a/cmd/podman/history.go b/cmd/podman/history.go index 4f3867d34..2570dcc7d 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -6,12 +6,11 @@ import ( "strings" "time" - "github.com/containers/image/types" units "github.com/docker/go-units" - "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/podman/formats" "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod/image" "github.com/urfave/cli" ) @@ -26,18 +25,6 @@ type historyTemplateParams struct { Comment string } -// historyJSONParams is only used when the JSON format is specified, -// and is better for data processing from JSON. -// historyJSONParams will be populated by data from v1.History and types.BlobInfo, -// the members of the struct are the sama data types as their sources. -type historyJSONParams struct { - ID string `json:"id"` - Created *time.Time `json:"created"` - CreatedBy string `json:"createdBy"` - Size int64 `json:"size"` - Comment string `json:"comment"` -} - // historyOptions stores cli flag values type historyOptions struct { human bool @@ -111,12 +98,12 @@ func historyCmd(c *cli.Context) error { format: format, } - history, layers, err := image.History(getContext()) + history, err := image.History(getContext()) if err != nil { return errors.Wrapf(err, "error getting history of image %q", image.InputName) } - return generateHistoryOutput(history, layers, image.ID(), opts) + return generateHistoryOutput(history, opts) } func genHistoryFormat(format string, quiet bool) string { @@ -132,7 +119,7 @@ func genHistoryFormat(format string, quiet bool) string { } // historyToGeneric makes an empty array of interfaces for output -func historyToGeneric(templParams []historyTemplateParams, JSONParams []historyJSONParams) (genericParams []interface{}) { +func historyToGeneric(templParams []historyTemplateParams, JSONParams []*image.History) (genericParams []interface{}) { if len(templParams) > 0 { for _, v := range templParams { genericParams = append(genericParams, interface{}(v)) @@ -158,36 +145,27 @@ func (h *historyTemplateParams) headerMap() map[string]string { } // getHistorytemplateOutput gets the modified history information to be printed in human readable format -func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) (historyOutput []historyTemplateParams) { +func getHistoryTemplateOutput(history []*image.History, opts historyOptions) (historyOutput []historyTemplateParams) { var ( outputSize string createdTime string createdBy string - count = 1 ) - for i := len(history) - 1; i >= 0; i-- { - if i != len(history)-1 { - imageID = "" - } - if !opts.noTrunc && i == len(history)-1 { + for _, hist := range history { + imageID := hist.ID + if !opts.noTrunc && imageID != "" { imageID = shortID(imageID) } - var size int64 - if !history[i].EmptyLayer { - size = layers[len(layers)-count].Size - count++ - } - if opts.human { - createdTime = units.HumanDuration(time.Since((*history[i].Created))) + " ago" - outputSize = units.HumanSize(float64(size)) + createdTime = units.HumanDuration(time.Since((*hist.Created))) + " ago" + outputSize = units.HumanSize(float64(hist.Size)) } else { - createdTime = (history[i].Created).Format(time.RFC3339) - outputSize = strconv.FormatInt(size, 10) + createdTime = (hist.Created).Format(time.RFC3339) + outputSize = strconv.FormatInt(hist.Size, 10) } - createdBy = strings.Join(strings.Fields(history[i].CreatedBy), " ") + createdBy = strings.Join(strings.Fields(hist.CreatedBy), " ") if !opts.noTrunc && len(createdBy) > createdByTruncLength { createdBy = createdBy[:createdByTruncLength-3] + "..." } @@ -197,29 +175,7 @@ func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, ima Created: createdTime, CreatedBy: createdBy, Size: outputSize, - Comment: history[i].Comment, - } - historyOutput = append(historyOutput, params) - } - return -} - -// getHistoryJSONOutput returns the history information in its raw form -func getHistoryJSONOutput(history []v1.History, layers []types.BlobInfo, imageID string) (historyOutput []historyJSONParams) { - count := 1 - for i := len(history) - 1; i >= 0; i-- { - var size int64 - if !history[i].EmptyLayer { - size = layers[len(layers)-count].Size - count++ - } - - params := historyJSONParams{ - ID: imageID, - Created: history[i].Created, - CreatedBy: history[i].CreatedBy, - Size: size, - Comment: history[i].Comment, + Comment: hist.Comment, } historyOutput = append(historyOutput, params) } @@ -227,7 +183,7 @@ func getHistoryJSONOutput(history []v1.History, layers []types.BlobInfo, imageID } // generateHistoryOutput generates the history based on the format given -func generateHistoryOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) error { +func generateHistoryOutput(history []*image.History, opts historyOptions) error { if len(history) == 0 { return nil } @@ -236,11 +192,10 @@ func generateHistoryOutput(history []v1.History, layers []types.BlobInfo, imageI switch opts.format { case formats.JSONString: - historyOutput := getHistoryJSONOutput(history, layers, imageID) - out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, historyOutput)} + out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, history)} default: - historyOutput := getHistoryTemplateOutput(history, layers, imageID, opts) - out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []historyJSONParams{}), Template: opts.format, Fields: historyOutput[0].headerMap()} + historyOutput := getHistoryTemplateOutput(history, opts) + out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []*image.History{}), Template: opts.format, Fields: historyOutput[0].headerMap()} } return formats.Writer(out).Out() diff --git a/libpod/image/image.go b/libpod/image/image.go index c8a929074..57eabe2c8 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -608,17 +608,87 @@ func (i *Image) Layer() (*storage.Layer, error) { return i.imageruntime.store.Layer(i.image.TopLayer) } +// History contains the history information of an image +type History struct { + ID string `json:"id"` + Created *time.Time `json:"created"` + CreatedBy string `json:"createdBy"` + Size int64 `json:"size"` + Comment string `json:"comment"` +} + // History gets the history of an image and information about its layers -func (i *Image) History(ctx context.Context) ([]ociv1.History, []types.BlobInfo, error) { +func (i *Image) History(ctx context.Context) ([]*History, error) { img, err := i.toImageRef(ctx) if err != nil { - return nil, nil, err + return nil, err } oci, err := img.OCIConfig(ctx) if err != nil { - return nil, nil, err + return nil, err + } + + // Get the IDs of the images making up the history layers + // if the images exist locally in the store + images, err := i.imageruntime.GetImages() + if err != nil { + return nil, errors.Wrapf(err, "error getting images from store") + } + imageIDs := []string{i.ID()} + if err := i.historyLayerIDs(i.TopLayer(), images, &imageIDs); err != nil { + return nil, errors.Wrap(err, "error getting image IDs for layers in history") } - return oci.History, img.LayerInfos(), nil + + var ( + imageID string + imgIDCount = 0 + size int64 + sizeCount = 1 + allHistory []*History + ) + + for i := len(oci.History) - 1; i >= 0; i-- { + if imgIDCount < len(imageIDs) { + imageID = imageIDs[imgIDCount] + imgIDCount++ + } else { + imageID = "" + } + if !oci.History[i].EmptyLayer { + size = img.LayerInfos()[len(img.LayerInfos())-sizeCount].Size + sizeCount++ + } + allHistory = append(allHistory, &History{ + ID: imageID, + Created: oci.History[i].Created, + CreatedBy: oci.History[i].CreatedBy, + Size: size, + Comment: oci.History[i].Comment, + }) + } + + return allHistory, nil +} + +// historyLayerIDs goes through the images in store and checks if the top layer of an image +// is the same as the parent of topLayerID +func (i *Image) historyLayerIDs(topLayerID string, images []*Image, IDs *[]string) error { + for _, image := range images { + // Get the layer info of topLayerID + layer, err := i.imageruntime.store.Layer(topLayerID) + if err != nil { + return errors.Wrapf(err, "error getting layer info %q", topLayerID) + } + // Check if the parent of layer is equal to the image's top layer + // If so add the image ID to the list of IDs and find the parent of + // the top layer of the image ID added to the list + // Since we are checking for parent, each top layer can only have one parent + if layer.Parent == image.TopLayer() { + *IDs = append(*IDs, image.ID()) + return i.historyLayerIDs(image.TopLayer(), images, IDs) + } + } + return nil } // Dangling returns a bool if the image is "dangling" diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 420962973..8e2669660 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -1,6 +1,7 @@ package varlinkapi import ( + "bytes" "encoding/json" "fmt" "io" @@ -8,7 +9,6 @@ import ( "strings" "time" - "bytes" "github.com/containers/image/docker" "github.com/containers/image/types" "github.com/docker/go-units" @@ -307,27 +307,19 @@ func (i *LibpodAPI) HistoryImage(call ioprojectatomicpodman.VarlinkCall, name st if err != nil { return call.ReplyImageNotFound(name) } - history, layerInfos, err := newImage.History(getContext()) + history, err := newImage.History(getContext()) if err != nil { return call.ReplyErrorOccurred(err.Error()) } - var ( - histories []ioprojectatomicpodman.ImageHistory - count = 1 - ) - for i := len(history) - 1; i >= 0; i-- { - var size int64 - if !history[i].EmptyLayer { - size = layerInfos[len(layerInfos)-count].Size - count++ - } + var histories []ioprojectatomicpodman.ImageHistory + for _, hist := range history { imageHistory := ioprojectatomicpodman.ImageHistory{ - Id: newImage.ID(), - Created: history[i].Created.String(), - CreatedBy: history[i].CreatedBy, + Id: hist.ID, + Created: hist.Created.String(), + CreatedBy: hist.CreatedBy, Tags: newImage.Names(), - Size: size, - Comment: history[i].Comment, + Size: hist.Size, + Comment: hist.Comment, } histories = append(histories, imageHistory) } -- cgit v1.2.3-54-g00ecf