Skip to content

Commit

Permalink
Podman history now prints out intermediate image IDs
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
umohnani8 committed Jun 22, 2018
1 parent bb4db6d commit 18ebbfa
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 84 deletions.
81 changes: 18 additions & 63 deletions cmd/podman/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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))
Expand All @@ -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] + "..."
}
Expand All @@ -197,37 +175,15 @@ 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)
}
return
}

// 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
}
Expand All @@ -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()
Expand Down
78 changes: 74 additions & 4 deletions libpod/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<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"
Expand Down
26 changes: 9 additions & 17 deletions pkg/varlinkapi/images.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package varlinkapi

import (
"bytes"
"encoding/json"
"fmt"
"io"
"path/filepath"
"strings"
"time"

"bytes"
"github.com/containers/image/docker"
"github.com/containers/image/types"
"github.com/docker/go-units"
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 18ebbfa

Please sign in to comment.