From a31073206021a424b13ef1398f75c8ff88157fc9 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Mon, 7 Oct 2019 12:09:31 -0400 Subject: [PATCH] add shared layer type (instead of interface) --- dive/image/analyzer.go | 2 +- dive/image/docker/image_archive.go | 5 +-- dive/image/docker/layer.go | 54 ++++------------------------ dive/image/image.go | 6 ++-- dive/image/layer.go | 38 +++++++++++++++----- dive/image/podman/image_directory.go | 7 ++-- dive/image/podman/layer.go | 47 +++++++----------------- runtime/export/export.go | 46 +++++++----------------- runtime/export/export_test.go | 2 +- runtime/export/file_reference.go | 7 ++++ runtime/export/image.go | 8 +++++ runtime/export/layer.go | 8 +++++ runtime/ui/details_controller.go | 4 +-- runtime/ui/layer_controller.go | 8 ++--- 14 files changed, 104 insertions(+), 138 deletions(-) create mode 100644 runtime/export/file_reference.go create mode 100644 runtime/export/image.go create mode 100644 runtime/export/layer.go diff --git a/dive/image/analyzer.go b/dive/image/analyzer.go index 90a8689..58b8da8 100644 --- a/dive/image/analyzer.go +++ b/dive/image/analyzer.go @@ -9,7 +9,7 @@ type Analyzer interface { } type AnalysisResult struct { - Layers []Layer + Layers []*Layer RefTrees []*filetree.FileTree Efficiency float64 SizeBytes uint64 diff --git a/dive/image/docker/image_archive.go b/dive/image/docker/image_archive.go index 5c4b001..106bd4a 100644 --- a/dive/image/docker/image_archive.go +++ b/dive/image/docker/image_archive.go @@ -147,7 +147,7 @@ func (img *ImageArchive) ToImage() (*image.Image, error) { } // build the layers array - layers := make([]image.Layer, len(trees)) + layers := make([]*image.Layer, len(trees)) // note that the resolver config stores images in reverse chronological order, so iterate backwards through layers // as you iterate chronologically through history (ignoring history items that have no layer contents) @@ -174,11 +174,12 @@ func (img *ImageArchive) ToImage() (*image.Image, error) { historyObj.Size = tree.FileSize - layers[layerIdx] = &layer{ + dockerLayer := layer{ history: historyObj, index: tarPathIdx, tree: trees[layerIdx], } + layers[layerIdx] = dockerLayer.ToLayer() tarPathIdx++ } diff --git a/dive/image/docker/layer.go b/dive/image/docker/layer.go index 9c423b5..c7581d9 100644 --- a/dive/image/docker/layer.go +++ b/dive/image/docker/layer.go @@ -1,11 +1,9 @@ package docker import ( - "fmt" "github.com/wagoodman/dive/dive/image" "strings" - "github.com/dustin/go-humanize" "github.com/wagoodman/dive/dive/filetree" ) @@ -16,52 +14,14 @@ type layer struct { tree *filetree.FileTree } -// ShortId returns the truncated id of the current layer. -func (l *layer) Id() string { - return l.history.ID -} - -// index returns the relative position of the layer within the image. -func (l *layer) Index() int { - return l.index -} - -// Size returns the number of bytes that this image is. -func (l *layer) Size() uint64 { - return l.history.Size -} - -// Tree returns the file tree representing the current layer. -func (l *layer) Tree() *filetree.FileTree { - return l.tree -} - -// ShortId returns the truncated id of the current layer. -func (l *layer) Command() string { - return strings.TrimPrefix(l.history.CreatedBy, "/bin/sh -c ") -} - -// ShortId returns the truncated id of the current layer. -func (l *layer) ShortId() string { - rangeBound := 15 - id := l.Id() - if length := len(id); length < 15 { - rangeBound = length - } - id = id[0:rangeBound] - - return id -} // String represents a layer in a columnar format. -func (l *layer) String() string { - - if l.index == 0 { - return fmt.Sprintf(image.LayerFormat, - humanize.Bytes(l.Size()), - "FROM "+l.ShortId()) +func (l *layer) ToLayer() *image.Layer { + return &image.Layer{ + Id: l.history.ID, + Index: l.index, + Command: strings.TrimPrefix(l.history.CreatedBy, "/bin/sh -c "), + Size: l.history.Size, + Tree: l.tree, } - return fmt.Sprintf(image.LayerFormat, - humanize.Bytes(l.Size()), - l.Command()) } diff --git a/dive/image/image.go b/dive/image/image.go index 66ed49a..e875efa 100644 --- a/dive/image/image.go +++ b/dive/image/image.go @@ -6,7 +6,7 @@ import ( type Image struct { Trees []*filetree.FileTree - Layers []Layer + Layers []*Layer } func (img *Image) Analyze() (*AnalysisResult, error) { @@ -15,9 +15,9 @@ func (img *Image) Analyze() (*AnalysisResult, error) { var sizeBytes, userSizeBytes uint64 for i, v := range img.Layers { - sizeBytes += v.Size() + sizeBytes += v.Size if i != 0 { - userSizeBytes += v.Size() + userSizeBytes += v.Size } } diff --git a/dive/image/layer.go b/dive/image/layer.go index 71e48e0..b92eabe 100644 --- a/dive/image/layer.go +++ b/dive/image/layer.go @@ -1,6 +1,8 @@ package image import ( + "fmt" + "github.com/dustin/go-humanize" "github.com/wagoodman/dive/dive/filetree" ) @@ -8,12 +10,32 @@ const ( LayerFormat = "%7s %s" ) -type Layer interface { - Id() string - ShortId() string - Index() int - Command() string - Size() uint64 - Tree() *filetree.FileTree - String() string +type Layer struct { + Id string + Index int + Command string + Size uint64 + Tree *filetree.FileTree +} + +func (l *Layer) ShortId() string { + rangeBound := 15 + id := l.Id + if length := len(id); length < 15 { + rangeBound = length + } + id = id[0:rangeBound] + + return id +} + +func (l *Layer) String() string { + if l.Index == 0 { + return fmt.Sprintf(LayerFormat, + humanize.Bytes(l.Size), + "FROM "+l.ShortId()) + } + return fmt.Sprintf(LayerFormat, + humanize.Bytes(l.Size), + l.Command) } diff --git a/dive/image/podman/image_directory.go b/dive/image/podman/image_directory.go index 8e551a8..4e2d1d5 100644 --- a/dive/image/podman/image_directory.go +++ b/dive/image/podman/image_directory.go @@ -110,17 +110,18 @@ func (img *ImageDirectoryRef) ToImage() (*image.Image, error) { return nil, fmt.Errorf("could not find '%s' in parsed trees", id) } - layers := make([]image.Layer, len(trees)) + layers := make([]*image.Layer, len(trees)) // note that the resolver config stores images in reverse chronological order, so iterate backwards through layers // as you iterate chronologically through history (ignoring history items that have no layer contents) - // Note: history is not required metadata in a docker image! + // Note: history is not required metadata in an OCI image! for layerIdx, id := range img.layerOrder { - layers[layerIdx] = &layer{ + podmanLayer := layer{ obj: img.layerMap[id], index: layerIdx, tree: trees[layerIdx], } + layers[layerIdx] = podmanLayer.ToLayer() } return &image.Image{ diff --git a/dive/image/podman/layer.go b/dive/image/podman/layer.go index 1323fcc..de173a5 100644 --- a/dive/image/podman/layer.go +++ b/dive/image/podman/layer.go @@ -1,9 +1,7 @@ package podman import ( - "fmt" podmanImage "github.com/containers/libpod/libpod/image" - "github.com/dustin/go-humanize" "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" "strings" @@ -16,36 +14,19 @@ type layer struct { tree *filetree.FileTree } -// ShortId returns the truncated id of the current layer. -func (l *layer) Id() string { - return l.obj.ID() -} - -// index returns the relative position of the layer within the image. -func (l *layer) Index() int { - return l.index -} - -// Size returns the number of bytes that this image is. -func (l *layer) Size() uint64 { - return uint64(l.obj.ImageData.Size) -} - -// Tree returns the file tree representing the current layer. -func (l *layer) Tree() *filetree.FileTree { - return l.tree -} - // ShortId returns the truncated id of the current layer. func (l *layer) Command() string { - // todo: is 0 right? - return strings.TrimPrefix(l.obj.ImageData.History[0].CreatedBy, "/bin/sh -c ") + if len(l.obj.ImageData.History) > 0 { + hist := l.obj.ImageData.History + return strings.TrimPrefix(hist[len(hist)-1].CreatedBy, "/bin/sh -c ") + } + return "unknown" } // ShortId returns the truncated id of the current layer. func (l *layer) ShortId() string { rangeBound := 15 - id := l.Id() + id := l.obj.ID() if length := len(id); length < 15 { rangeBound = length } @@ -55,14 +36,12 @@ func (l *layer) ShortId() string { } // String represents a layer in a columnar format. -func (l *layer) String() string { - - if l.index == 0 { - return fmt.Sprintf(image.LayerFormat, - humanize.Bytes(l.Size()), - "FROM "+l.ShortId()) +func (l *layer) ToLayer() *image.Layer { + return &image.Layer{ + Id: l.obj.ID(), + Index: l.index, + Command: l.Command(), + Size: uint64(l.obj.ImageData.Size), + Tree: l.tree, } - return fmt.Sprintf(image.LayerFormat, - humanize.Bytes(l.Size()), - l.Command()) } diff --git a/runtime/export/export.go b/runtime/export/export.go index 58aa095..f94ac8b 100644 --- a/runtime/export/export.go +++ b/runtime/export/export.go @@ -2,50 +2,30 @@ package export import ( "encoding/json" - "github.com/wagoodman/dive/dive/image" + diveImage "github.com/wagoodman/dive/dive/image" "io/ioutil" ) type export struct { - Layer []exportLayer `json:"layer"` - Image exportImage `json:"image"` + Layer []layer `json:"layer"` + Image image `json:"image"` } -type exportLayer struct { - Index int `json:"index"` - DigestID string `json:"digestId"` - SizeBytes uint64 `json:"sizeBytes"` - Command string `json:"command"` -} - -type exportImage struct { - SizeBytes uint64 `json:"sizeBytes"` - InefficientBytes uint64 `json:"inefficientBytes"` - EfficiencyScore float64 `json:"efficiencyScore"` - InefficientFiles []exportReferenceFile `json:"exportReferenceFile"` -} - -type exportReferenceFile struct { - References int `json:"count"` - SizeBytes uint64 `json:"sizeBytes"` - Path string `json:"file"` -} - -func NewExport(analysis *image.AnalysisResult) *export { +func NewExport(analysis *diveImage.AnalysisResult) *export { data := export{} - data.Layer = make([]exportLayer, len(analysis.Layers)) - data.Image.InefficientFiles = make([]exportReferenceFile, len(analysis.Inefficiencies)) + data.Layer = make([]layer, len(analysis.Layers)) + data.Image.InefficientFiles = make([]fileReference, len(analysis.Inefficiencies)) // export layers in order for revIdx := len(analysis.Layers) - 1; revIdx >= 0; revIdx-- { - layer := analysis.Layers[revIdx] + curLayer := analysis.Layers[revIdx] idx := (len(analysis.Layers) - 1) - revIdx - data.Layer[idx] = exportLayer{ - Index: layer.Index(), - DigestID: layer.Id(), - SizeBytes: layer.Size(), - Command: layer.Command(), + data.Layer[idx] = layer{ + Index: curLayer.Index, + DigestID: curLayer.Id, + SizeBytes: curLayer.Size, + Command: curLayer.Command, } } @@ -56,7 +36,7 @@ func NewExport(analysis *image.AnalysisResult) *export { for idx := 0; idx < len(analysis.Inefficiencies); idx++ { fileData := analysis.Inefficiencies[len(analysis.Inefficiencies)-1-idx] - data.Image.InefficientFiles[idx] = exportReferenceFile{ + data.Image.InefficientFiles[idx] = fileReference{ References: len(fileData.Nodes), SizeBytes: uint64(fileData.CumulativeSize), Path: fileData.Path, diff --git a/runtime/export/export_test.go b/runtime/export/export_test.go index e3750ed..c7cf97d 100644 --- a/runtime/export/export_test.go +++ b/runtime/export/export_test.go @@ -108,7 +108,7 @@ func Test_Export(t *testing.T) { "sizeBytes": 1220598, "inefficientBytes": 32025, "efficiencyScore": 0.9844212134184309, - "exportReferenceFile": [ + "fileReference": [ { "count": 2, "sizeBytes": 12810, diff --git a/runtime/export/file_reference.go b/runtime/export/file_reference.go new file mode 100644 index 0000000..14aac0e --- /dev/null +++ b/runtime/export/file_reference.go @@ -0,0 +1,7 @@ +package export + +type fileReference struct { + References int `json:"count"` + SizeBytes uint64 `json:"sizeBytes"` + Path string `json:"file"` +} diff --git a/runtime/export/image.go b/runtime/export/image.go new file mode 100644 index 0000000..2052371 --- /dev/null +++ b/runtime/export/image.go @@ -0,0 +1,8 @@ +package export + +type image struct { + SizeBytes uint64 `json:"sizeBytes"` + InefficientBytes uint64 `json:"inefficientBytes"` + EfficiencyScore float64 `json:"efficiencyScore"` + InefficientFiles []fileReference `json:"fileReference"` +} diff --git a/runtime/export/layer.go b/runtime/export/layer.go new file mode 100644 index 0000000..b360f48 --- /dev/null +++ b/runtime/export/layer.go @@ -0,0 +1,8 @@ +package export + +type layer struct { + Index int `json:"index"` + DigestID string `json:"digestId"` + SizeBytes uint64 `json:"sizeBytes"` + Command string `json:"command"` +} diff --git a/runtime/ui/details_controller.go b/runtime/ui/details_controller.go index a9ec51c..f1d9c5b 100644 --- a/runtime/ui/details_controller.go +++ b/runtime/ui/details_controller.go @@ -131,9 +131,9 @@ func (controller *DetailsController) Render() error { controller.view.Clear() var lines = make([]string, 0) - lines = append(lines, Formatting.Header("Digest: ")+currentLayer.Id()) + lines = append(lines, Formatting.Header("Digest: ")+currentLayer.Id) lines = append(lines, Formatting.Header("Command:")) - lines = append(lines, currentLayer.Command()) + lines = append(lines, currentLayer.Command) lines = append(lines, "\n"+Formatting.Header(vtclean.Clean(imageHeaderStr, false))) lines = append(lines, imageSizeStr) lines = append(lines, wastedSpaceStr) diff --git a/runtime/ui/layer_controller.go b/runtime/ui/layer_controller.go index b7c3740..b1868c9 100644 --- a/runtime/ui/layer_controller.go +++ b/runtime/ui/layer_controller.go @@ -20,7 +20,7 @@ type LayerController struct { view *gocui.View header *gocui.View LayerIndex int - Layers []image.Layer + Layers []*image.Layer CompareMode CompareType CompareStartIndex int ImageSize uint64 @@ -32,7 +32,7 @@ type LayerController struct { } // NewLayerController creates a new view object attached the the global [gocui] screen object. -func NewLayerController(name string, gui *gocui.Gui, layers []image.Layer) (controller *LayerController, err error) { +func NewLayerController(name string, gui *gocui.Gui, layers []*image.Layer) (controller *LayerController, err error) { controller = new(LayerController) // populate main fields @@ -209,7 +209,7 @@ func (controller *LayerController) SetCursor(layer int) error { } // currentLayer returns the Layer object currently selected. -func (controller *LayerController) currentLayer() image.Layer { +func (controller *LayerController) currentLayer() *image.Layer { return controller.Layers[(len(controller.Layers)-1)-controller.LayerIndex] } @@ -262,7 +262,7 @@ func (controller *LayerController) renderCompareBar(layerIdx int) string { func (controller *LayerController) Update() error { controller.ImageSize = 0 for idx := 0; idx < len(controller.Layers); idx++ { - controller.ImageSize += controller.Layers[idx].Size() + controller.ImageSize += controller.Layers[idx].Size } return nil }