mirror of
https://github.com/containers/podman.git
synced 2025-06-20 09:03:43 +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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/image/types"
|
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/projectatomic/libpod/cmd/podman/formats"
|
"github.com/projectatomic/libpod/cmd/podman/formats"
|
||||||
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
|
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
|
||||||
|
"github.com/projectatomic/libpod/libpod/image"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,18 +25,6 @@ type historyTemplateParams struct {
|
|||||||
Comment string
|
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
|
// historyOptions stores cli flag values
|
||||||
type historyOptions struct {
|
type historyOptions struct {
|
||||||
human bool
|
human bool
|
||||||
@ -111,12 +98,12 @@ func historyCmd(c *cli.Context) error {
|
|||||||
format: format,
|
format: format,
|
||||||
}
|
}
|
||||||
|
|
||||||
history, layers, err := image.History(getContext())
|
history, err := image.History(getContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error getting history of image %q", image.InputName)
|
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 {
|
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
|
// 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 {
|
if len(templParams) > 0 {
|
||||||
for _, v := range templParams {
|
for _, v := range templParams {
|
||||||
genericParams = append(genericParams, interface{}(v))
|
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
|
// 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 (
|
var (
|
||||||
outputSize string
|
outputSize string
|
||||||
createdTime string
|
createdTime string
|
||||||
createdBy string
|
createdBy string
|
||||||
count = 1
|
|
||||||
)
|
)
|
||||||
for i := len(history) - 1; i >= 0; i-- {
|
for _, hist := range history {
|
||||||
if i != len(history)-1 {
|
imageID := hist.ID
|
||||||
imageID = "<missing>"
|
if !opts.noTrunc && imageID != "<missing>" {
|
||||||
}
|
|
||||||
if !opts.noTrunc && i == len(history)-1 {
|
|
||||||
imageID = shortID(imageID)
|
imageID = shortID(imageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var size int64
|
|
||||||
if !history[i].EmptyLayer {
|
|
||||||
size = layers[len(layers)-count].Size
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.human {
|
if opts.human {
|
||||||
createdTime = units.HumanDuration(time.Since((*history[i].Created))) + " ago"
|
createdTime = units.HumanDuration(time.Since((*hist.Created))) + " ago"
|
||||||
outputSize = units.HumanSize(float64(size))
|
outputSize = units.HumanSize(float64(hist.Size))
|
||||||
} else {
|
} else {
|
||||||
createdTime = (history[i].Created).Format(time.RFC3339)
|
createdTime = (hist.Created).Format(time.RFC3339)
|
||||||
outputSize = strconv.FormatInt(size, 10)
|
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 {
|
if !opts.noTrunc && len(createdBy) > createdByTruncLength {
|
||||||
createdBy = createdBy[:createdByTruncLength-3] + "..."
|
createdBy = createdBy[:createdByTruncLength-3] + "..."
|
||||||
}
|
}
|
||||||
@ -197,29 +175,7 @@ func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, ima
|
|||||||
Created: createdTime,
|
Created: createdTime,
|
||||||
CreatedBy: createdBy,
|
CreatedBy: createdBy,
|
||||||
Size: outputSize,
|
Size: outputSize,
|
||||||
Comment: history[i].Comment,
|
Comment: hist.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,
|
|
||||||
}
|
}
|
||||||
historyOutput = append(historyOutput, params)
|
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
|
// 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 {
|
if len(history) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -236,11 +192,10 @@ func generateHistoryOutput(history []v1.History, layers []types.BlobInfo, imageI
|
|||||||
|
|
||||||
switch opts.format {
|
switch opts.format {
|
||||||
case formats.JSONString:
|
case formats.JSONString:
|
||||||
historyOutput := getHistoryJSONOutput(history, layers, imageID)
|
out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, history)}
|
||||||
out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, historyOutput)}
|
|
||||||
default:
|
default:
|
||||||
historyOutput := getHistoryTemplateOutput(history, layers, imageID, opts)
|
historyOutput := getHistoryTemplateOutput(history, opts)
|
||||||
out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []historyJSONParams{}), Template: opts.format, Fields: historyOutput[0].headerMap()}
|
out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []*image.History{}), Template: opts.format, Fields: historyOutput[0].headerMap()}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formats.Writer(out).Out()
|
return formats.Writer(out).Out()
|
||||||
|
@ -608,17 +608,87 @@ func (i *Image) Layer() (*storage.Layer, error) {
|
|||||||
return i.imageruntime.store.Layer(i.image.TopLayer)
|
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
|
// 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)
|
img, err := i.toImageRef(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
oci, err := img.OCIConfig(ctx)
|
oci, err := img.OCIConfig(ctx)
|
||||||
if err != nil {
|
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"
|
// Dangling returns a bool if the image is "dangling"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package varlinkapi
|
package varlinkapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -8,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"github.com/containers/image/docker"
|
"github.com/containers/image/docker"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
@ -307,27 +307,19 @@ func (i *LibpodAPI) HistoryImage(call ioprojectatomicpodman.VarlinkCall, name st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyImageNotFound(name)
|
return call.ReplyImageNotFound(name)
|
||||||
}
|
}
|
||||||
history, layerInfos, err := newImage.History(getContext())
|
history, err := newImage.History(getContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
}
|
}
|
||||||
var (
|
var histories []ioprojectatomicpodman.ImageHistory
|
||||||
histories []ioprojectatomicpodman.ImageHistory
|
for _, hist := range history {
|
||||||
count = 1
|
|
||||||
)
|
|
||||||
for i := len(history) - 1; i >= 0; i-- {
|
|
||||||
var size int64
|
|
||||||
if !history[i].EmptyLayer {
|
|
||||||
size = layerInfos[len(layerInfos)-count].Size
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
imageHistory := ioprojectatomicpodman.ImageHistory{
|
imageHistory := ioprojectatomicpodman.ImageHistory{
|
||||||
Id: newImage.ID(),
|
Id: hist.ID,
|
||||||
Created: history[i].Created.String(),
|
Created: hist.Created.String(),
|
||||||
CreatedBy: history[i].CreatedBy,
|
CreatedBy: hist.CreatedBy,
|
||||||
Tags: newImage.Names(),
|
Tags: newImage.Names(),
|
||||||
Size: size,
|
Size: hist.Size,
|
||||||
Comment: history[i].Comment,
|
Comment: hist.Comment,
|
||||||
}
|
}
|
||||||
histories = append(histories, imageHistory)
|
histories = append(histories, imageHistory)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user