mirror of
https://github.com/containers/podman.git
synced 2025-06-19 16:33:24 +08:00
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:
@ -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()
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user