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 <umohnani@redhat.com>

Closes: #982
Approved by: mheon
This commit is contained in:
umohnani8
2018-06-21 16:11:59 -04:00
committed by Atomic Bot
parent 89af35175d
commit 088d5af879
3 changed files with 101 additions and 84 deletions

View File

@ -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 = "<missing>"
}
if !opts.noTrunc && i == len(history)-1 {
for _, hist := range history {
imageID := hist.ID
if !opts.noTrunc && imageID != "<missing>" {
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()

View File

@ -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
}
return oci.History, img.LayerInfos(), nil
// 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")
}
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 = "<missing>"
}
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"

View File

@ -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)
}