From 4ab93c854e5f1cfbe0f24164fd07940902ac0b22 Mon Sep 17 00:00:00 2001 From: peter-evans Date: Thu, 6 Sep 2018 19:55:09 +0900 Subject: [PATCH 1/3] Add image and layer size analyzer --- differs/differs.go | 26 ++--- differs/size_diff.go | 122 ++++++++++++++++++++++++ tests/integration_test.go | 30 ++++++ tests/size_analysis_expected.json | 12 +++ tests/size_diff_expected.json | 14 +++ tests/size_layer_analysis_expected.json | 20 ++++ tests/size_layer_diff_expected.json | 24 +++++ util/analyze_output_utils.go | 33 +++++++ util/diff_output_utils.go | 36 +++++++ util/format_utils.go | 2 + util/template_utils.go | 16 ++++ 11 files changed, 324 insertions(+), 11 deletions(-) create mode 100644 differs/size_diff.go create mode 100644 tests/size_analysis_expected.json create mode 100644 tests/size_diff_expected.json create mode 100644 tests/size_layer_analysis_expected.json create mode 100644 tests/size_layer_diff_expected.json diff --git a/differs/differs.go b/differs/differs.go index 2a187ee1..c810fa05 100644 --- a/differs/differs.go +++ b/differs/differs.go @@ -28,6 +28,8 @@ const historyAnalyzer = "history" const metadataAnalyzer = "metadata" const fileAnalyzer = "file" const layerAnalyzer = "layer" +const sizeAnalyzer = "size" +const sizeLayerAnalyzer = "sizelayer" const aptAnalyzer = "apt" const aptLayerAnalyzer = "aptlayer" const rpmAnalyzer = "rpm" @@ -53,19 +55,21 @@ type Analyzer interface { } var Analyzers = map[string]Analyzer{ - historyAnalyzer: HistoryAnalyzer{}, - metadataAnalyzer: MetadataAnalyzer{}, - fileAnalyzer: FileAnalyzer{}, - layerAnalyzer: FileLayerAnalyzer{}, - aptAnalyzer: AptAnalyzer{}, - aptLayerAnalyzer: AptLayerAnalyzer{}, - rpmAnalyzer: RPMAnalyzer{}, - rpmLayerAnalyzer: RPMLayerAnalyzer{}, - pipAnalyzer: PipAnalyzer{}, - nodeAnalyzer: NodeAnalyzer{}, + historyAnalyzer: HistoryAnalyzer{}, + metadataAnalyzer: MetadataAnalyzer{}, + fileAnalyzer: FileAnalyzer{}, + layerAnalyzer: FileLayerAnalyzer{}, + sizeAnalyzer: SizeAnalyzer{}, + sizeLayerAnalyzer: SizeLayerAnalyzer{}, + aptAnalyzer: AptAnalyzer{}, + aptLayerAnalyzer: AptLayerAnalyzer{}, + rpmAnalyzer: RPMAnalyzer{}, + rpmLayerAnalyzer: RPMLayerAnalyzer{}, + pipAnalyzer: PipAnalyzer{}, + nodeAnalyzer: NodeAnalyzer{}, } -var LayerAnalyzers = [...]string{layerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer} +var LayerAnalyzers = [...]string{layerAnalyzer, sizeLayerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer} func (req DiffRequest) GetDiff() (map[string]util.Result, error) { img1 := req.Image1 diff --git a/differs/size_diff.go b/differs/size_diff.go new file mode 100644 index 00000000..fe45b1c5 --- /dev/null +++ b/differs/size_diff.go @@ -0,0 +1,122 @@ +/* +Copyright 2018 Google, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package differs + +import ( + "strconv" + + pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util" + "github.com/GoogleContainerTools/container-diff/util" +) + +type SizeAnalyzer struct { +} + +func (a SizeAnalyzer) Name() string { + return "SizeAnalyzer" +} + +// SizeDiff diffs two images and compares their size +func (a SizeAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { + diff := []util.EntryDiff{ + { + Name: "*", + Size1: pkgutil.GetSize(image1.FSPath), + Size2: pkgutil.GetSize(image2.FSPath), + }, + } + + return &util.SizeDiffResult{ + Image1: image1.Source, + Image2: image2.Source, + DiffType: "Size", + Diff: diff, + }, nil +} + +func (a SizeAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) { + entries := []pkgutil.DirectoryEntry{ + { + Name: "*", + Size: pkgutil.GetSize(image.FSPath), + }, + } + + return &util.SizeAnalyzeResult{ + Image: image.Source, + AnalyzeType: "Size", + Analysis: entries, + }, nil +} + +type SizeLayerAnalyzer struct { +} + +func (a SizeLayerAnalyzer) Name() string { + return "SizeLayerAnalyzer" +} + +// SizeLayerDiff diffs the layers of two images and compares their size +func (a SizeLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { + var layerDiffs []util.EntryDiff + + maxLayer := len(image1.Layers) + if len(image2.Layers) > maxLayer { + maxLayer = len(image2.Layers) + } + + for index := 0; index < maxLayer; index++ { + var size1, size2 int64 = -1, -1 + if index < len(image1.Layers) { + size1 = pkgutil.GetSize(image1.Layers[index].FSPath) + } + if index < len(image2.Layers) { + size2 = pkgutil.GetSize(image2.Layers[index].FSPath) + } + + diff := util.EntryDiff{ + Name: strconv.Itoa(index), + Size1: size1, + Size2: size2, + } + layerDiffs = append(layerDiffs, diff) + } + + return &util.SizeDiffResult{ + Image1: image1.Source, + Image2: image2.Source, + DiffType: "SizeLayer", + Diff: layerDiffs, + }, nil +} + +func (a SizeLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) { + var entries []pkgutil.DirectoryEntry + for index, layer := range image.Layers { + entry := pkgutil.DirectoryEntry{ + Name: strconv.Itoa(index), + Size: pkgutil.GetSize(layer.FSPath), + } + entries = append(entries, entry) + } + + return &util.SizeAnalyzeResult{ + Image: image.Source, + AnalyzeType: "SizeLayer", + Analysis: entries, + }, nil +} diff --git a/tests/integration_test.go b/tests/integration_test.go index 8d6b7ebb..f06899f6 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -117,6 +117,22 @@ func TestDiffAndAnalysis(t *testing.T) { differFlags: []string{"--type=layer", "--no-cache"}, expectedFile: "file_layer_diff_expected.json", }, + { + description: "size differ", + subcommand: "diff", + imageA: diffLayerBase, + imageB: diffLayerModifed, + differFlags: []string{"--type=size", "--no-cache"}, + expectedFile: "size_diff_expected.json", + }, + { + description: "size layer differ", + subcommand: "diff", + imageA: diffLayerBase, + imageB: diffLayerModifed, + differFlags: []string{"--type=sizelayer", "--no-cache"}, + expectedFile: "size_layer_diff_expected.json", + }, { description: "apt differ", subcommand: "diff", @@ -209,6 +225,20 @@ func TestDiffAndAnalysis(t *testing.T) { differFlags: []string{"--type=layer", "--no-cache"}, expectedFile: "file_layer_analysis_expected.json", }, + { + description: "size analysis", + subcommand: "analyze", + imageA: diffBase, + differFlags: []string{"--type=size", "--no-cache"}, + expectedFile: "size_analysis_expected.json", + }, + { + description: "size layer analysis", + subcommand: "analyze", + imageA: diffLayerBase, + differFlags: []string{"--type=sizelayer", "--no-cache"}, + expectedFile: "size_layer_analysis_expected.json", + }, { description: "pip analysis", subcommand: "analyze", diff --git a/tests/size_analysis_expected.json b/tests/size_analysis_expected.json new file mode 100644 index 00000000..c2307b8c --- /dev/null +++ b/tests/size_analysis_expected.json @@ -0,0 +1,12 @@ +[ + { + "Image": "gcr.io/gcp-runtimes/diff-base", + "AnalyzeType": "Size", + "Analysis": [ + { + "Name": "*", + "Size": 383043168 + } + ] + } +] \ No newline at end of file diff --git a/tests/size_diff_expected.json b/tests/size_diff_expected.json new file mode 100644 index 00000000..f1272c4c --- /dev/null +++ b/tests/size_diff_expected.json @@ -0,0 +1,14 @@ +[ + { + "Image1": "gcr.io/gcp-runtimes/diff-layer-base", + "Image2": "gcr.io/gcp-runtimes/diff-layer-modified", + "DiffType": "Size", + "Diff": [ + { + "Name": "*", + "Size1": 19, + "Size2": 15 + } + ] + } +] \ No newline at end of file diff --git a/tests/size_layer_analysis_expected.json b/tests/size_layer_analysis_expected.json new file mode 100644 index 00000000..e03bce36 --- /dev/null +++ b/tests/size_layer_analysis_expected.json @@ -0,0 +1,20 @@ +[ + { + "Image": "gcr.io/gcp-runtimes/diff-layer-base", + "AnalyzeType": "SizeLayer", + "Analysis": [ + { + "Name": "0", + "Size": 6 + }, + { + "Name": "1", + "Size": 7 + }, + { + "Name": "2", + "Size": 6 + } + ] + } +] \ No newline at end of file diff --git a/tests/size_layer_diff_expected.json b/tests/size_layer_diff_expected.json new file mode 100644 index 00000000..a0f80991 --- /dev/null +++ b/tests/size_layer_diff_expected.json @@ -0,0 +1,24 @@ +[ + { + "Image1": "gcr.io/gcp-runtimes/diff-layer-base", + "Image2": "gcr.io/gcp-runtimes/diff-layer-modified", + "DiffType": "SizeLayer", + "Diff": [ + { + "Name": "0", + "Size1": 6, + "Size2": 6 + }, + { + "Name": "1", + "Size1": 7, + "Size2": 9 + }, + { + "Name": "2", + "Size1": 6, + "Size2": -1 + } + ] + } +] \ No newline at end of file diff --git a/util/analyze_output_utils.go b/util/analyze_output_utils.go index 3c7a006e..ac5ef0d0 100644 --- a/util/analyze_output_utils.go +++ b/util/analyze_output_utils.go @@ -340,3 +340,36 @@ func (r FileLayerAnalyzeResult) OutputText(analyzeType string, format string) er } return TemplateOutputFromFormat(strResult, "FileLayerAnalyze", format) } + +type SizeAnalyzeResult AnalyzeResult + +func (r SizeAnalyzeResult) OutputStruct() interface{} { + analysis, valid := r.Analysis.([]util.DirectoryEntry) + if !valid { + logrus.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry") + return errors.New("Could not output SizeAnalyzer analysis result") + } + r.Analysis = analysis + return r +} + +func (r SizeAnalyzeResult) OutputText(analyzeType string, format string) error { + analysis, valid := r.Analysis.([]util.DirectoryEntry) + if !valid { + logrus.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry") + return errors.New("Could not output SizeAnalyzer analysis result") + } + + strAnalysis := stringifyDirectoryEntries(analysis) + + strResult := struct { + Image string + AnalyzeType string + Analysis []StrDirectoryEntry + }{ + Image: r.Image, + AnalyzeType: r.AnalyzeType, + Analysis: strAnalysis, + } + return TemplateOutputFromFormat(strResult, "SizeAnalyze", format) +} diff --git a/util/diff_output_utils.go b/util/diff_output_utils.go index c8e58b55..718dce27 100644 --- a/util/diff_output_utils.go +++ b/util/diff_output_utils.go @@ -297,6 +297,42 @@ func (r DirDiffResult) OutputText(diffType string, format string) error { return TemplateOutputFromFormat(strResult, "DirDiff", format) } +type SizeDiffResult DiffResult + +func (r SizeDiffResult) OutputStruct() interface{} { + diff, valid := r.Diff.([]EntryDiff) + if !valid { + logrus.Error("Unexpected structure of Diff. Should be of type []EntryDiff") + return errors.New("Could not output SizeAnalyzer diff result") + } + + r.Diff = diff + return r +} + +func (r SizeDiffResult) OutputText(diffType string, format string) error { + diff, valid := r.Diff.([]EntryDiff) + if !valid { + logrus.Error("Unexpected structure of Diff. Should be of type []EntryDiff") + return errors.New("Could not output SizeAnalyzer diff result") + } + + strDiff := stringifyEntryDiffs(diff) + + strResult := struct { + Image1 string + Image2 string + DiffType string + Diff []StrEntryDiff + }{ + Image1: r.Image1, + Image2: r.Image2, + DiffType: r.DiffType, + Diff: strDiff, + } + return TemplateOutputFromFormat(strResult, "SizeDiff", format) +} + type MultipleDirDiffResult DiffResult func (r MultipleDirDiffResult) OutputStruct() interface{} { diff --git a/util/format_utils.go b/util/format_utils.go index 2e93eb6c..0b374d30 100644 --- a/util/format_utils.go +++ b/util/format_utils.go @@ -39,6 +39,8 @@ var templates = map[string]string{ "ListAnalyze": ListAnalysisOutput, "FileAnalyze": FileAnalysisOutput, "FileLayerAnalyze": FileLayerAnalysisOutput, + "SizeAnalyze": SizeAnalysisOutput, + "SizeDiff": SizeDiffOutput, "MultiVersionPackageAnalyze": MultiVersionPackageOutput, "SingleVersionPackageAnalyze": SingleVersionPackageOutput, "SingleVersionPackageLayerAnalyze": SingleVersionPackageLayerOutput, diff --git a/util/template_utils.go b/util/template_utils.go index 14da15fb..42303a3d 100644 --- a/util/template_utils.go +++ b/util/template_utils.go @@ -100,6 +100,14 @@ const FilenameDiffOutput = ` {{.Diff}} ` +const SizeDiffOutput = ` +-----{{.DiffType}}----- + +Size differences between {{.Image1}} and {{.Image2}}:{{if not .Diff}} None{{else}} +LAYER SIZE1 SIZE2{{range .Diff}}{{"\n"}}{{.Name}} {{.Size1}} {{.Size2}}{{end}} +{{end}} +` + const ListAnalysisOutput = ` -----{{.AnalyzeType}}----- @@ -124,6 +132,14 @@ FILE SIZE{{range $analysis}}{{"\n"}}{{.Name}} {{.Size}}{{end}} {{end}} ` +const SizeAnalysisOutput = ` +-----{{.AnalyzeType}}----- + +Analysis for {{.Image}}:{{if not .Analysis}} None{{else}} +LAYER SIZE{{range .Analysis}}{{"\n"}}{{.Name}} {{.Size}}{{end}} +{{end}} +` + const MultiVersionPackageOutput = ` -----{{.AnalyzeType}}----- From bbb768c823e0958524dae93530687a7d6e5bc2dc Mon Sep 17 00:00:00 2001 From: peter-evans Date: Tue, 25 Sep 2018 11:26:51 +0900 Subject: [PATCH 2/3] Modify image and layer size analyzer --- cmd/root.go | 16 +++++--- differs/size_diff.go | 49 ++++++++++++++----------- pkg/util/image_utils.go | 2 + tests/size_analysis_expected.json | 3 +- tests/size_diff_expected.json | 2 +- tests/size_layer_analysis_expected.json | 3 ++ tests/size_layer_diff_expected.json | 5 --- util/analyze_output_utils.go | 45 ++++++++++++++++++++--- util/diff_output_utils.go | 48 +++++++++++++++++++++--- util/format_utils.go | 2 + util/output_text_utils.go | 28 ++++++++++++++ util/size_utils.go | 31 ++++++++++++++++ util/template_utils.go | 20 +++++++++- 13 files changed, 207 insertions(+), 47 deletions(-) create mode 100644 util/size_utils.go diff --git a/cmd/root.go b/cmd/root.go index 25180e5f..1f75cbf7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -208,6 +208,7 @@ func getImageForName(imageName string) (pkgutil.Image, error) { } layers = append(layers, pkgutil.Layer{ FSPath: path, + Digest: digest, }) elapsed := time.Now().Sub(layerStart) logrus.Infof("time elapsed retrieving layer: %fs", elapsed.Seconds()) @@ -216,7 +217,11 @@ func getImageForName(imageName string) (pkgutil.Image, error) { logrus.Infof("time elapsed retrieving image layers: %fs", elapsed.Seconds()) } - path, err := getExtractPathForImage(imageName, img) + imageDigest, err := getImageDigest(img) + if err != nil { + return pkgutil.Image{}, err + } + path, err := getExtractPathForName(pkgutil.RemoveTag(imageName) + "@" + imageDigest.String()) if err != nil { return pkgutil.Image{}, err } @@ -231,19 +236,20 @@ func getImageForName(imageName string) (pkgutil.Image, error) { Image: img, Source: imageName, FSPath: path, + Digest: imageDigest, Layers: layers, }, nil } -func getExtractPathForImage(imageName string, image v1.Image) (string, error) { +func getImageDigest(image v1.Image) (digest v1.Hash, err error) { start := time.Now() - digest, err := image.Digest() + digest, err = image.Digest() if err != nil { - return "", err + return digest, err } elapsed := time.Now().Sub(start) logrus.Infof("time elapsed retrieving image digest: %fs", elapsed.Seconds()) - return getExtractPathForName(pkgutil.RemoveTag(imageName) + "@" + digest.String()) + return digest, nil } func getExtractPathForName(name string) (string, error) { diff --git a/differs/size_diff.go b/differs/size_diff.go index fe45b1c5..5717a64e 100644 --- a/differs/size_diff.go +++ b/differs/size_diff.go @@ -32,12 +32,15 @@ func (a SizeAnalyzer) Name() string { // SizeDiff diffs two images and compares their size func (a SizeAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { - diff := []util.EntryDiff{ - { - Name: "*", - Size1: pkgutil.GetSize(image1.FSPath), - Size2: pkgutil.GetSize(image2.FSPath), - }, + diff := []util.SizeDiff{} + size1 := pkgutil.GetSize(image1.FSPath) + size2 := pkgutil.GetSize(image2.FSPath) + + if size1 != size2 { + diff = append(diff, util.SizeDiff{ + Size1: size1, + Size2: size2, + }) } return &util.SizeDiffResult{ @@ -49,10 +52,11 @@ func (a SizeAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { } func (a SizeAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) { - entries := []pkgutil.DirectoryEntry{ + entries := []util.SizeEntry{ { - Name: "*", - Size: pkgutil.GetSize(image.FSPath), + Name: image.Source, + Digest: image.Digest, + Size: pkgutil.GetSize(image.FSPath), }, } @@ -72,7 +76,7 @@ func (a SizeLayerAnalyzer) Name() string { // SizeLayerDiff diffs the layers of two images and compares their size func (a SizeLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) { - var layerDiffs []util.EntryDiff + var layerDiffs []util.SizeDiff maxLayer := len(image1.Layers) if len(image2.Layers) > maxLayer { @@ -88,15 +92,17 @@ func (a SizeLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, erro size2 = pkgutil.GetSize(image2.Layers[index].FSPath) } - diff := util.EntryDiff{ - Name: strconv.Itoa(index), - Size1: size1, - Size2: size2, + if size1 != size2 { + diff := util.SizeDiff{ + Name: strconv.Itoa(index), + Size1: size1, + Size2: size2, + } + layerDiffs = append(layerDiffs, diff) } - layerDiffs = append(layerDiffs, diff) } - return &util.SizeDiffResult{ + return &util.SizeLayerDiffResult{ Image1: image1.Source, Image2: image2.Source, DiffType: "SizeLayer", @@ -105,16 +111,17 @@ func (a SizeLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, erro } func (a SizeLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) { - var entries []pkgutil.DirectoryEntry + var entries []util.SizeEntry for index, layer := range image.Layers { - entry := pkgutil.DirectoryEntry{ - Name: strconv.Itoa(index), - Size: pkgutil.GetSize(layer.FSPath), + entry := util.SizeEntry{ + Name: strconv.Itoa(index), + Digest: layer.Digest, + Size: pkgutil.GetSize(layer.FSPath), } entries = append(entries, entry) } - return &util.SizeAnalyzeResult{ + return &util.SizeLayerAnalyzeResult{ Image: image.Source, AnalyzeType: "SizeLayer", Analysis: entries, diff --git a/pkg/util/image_utils.go b/pkg/util/image_utils.go index e2c628b9..875d280b 100644 --- a/pkg/util/image_utils.go +++ b/pkg/util/image_utils.go @@ -37,12 +37,14 @@ const tagRegexStr = ".*:([^/]+$)" type Layer struct { FSPath string + Digest v1.Hash } type Image struct { Image v1.Image Source string FSPath string + Digest v1.Hash Layers []Layer } diff --git a/tests/size_analysis_expected.json b/tests/size_analysis_expected.json index c2307b8c..a535bb0a 100644 --- a/tests/size_analysis_expected.json +++ b/tests/size_analysis_expected.json @@ -4,7 +4,8 @@ "AnalyzeType": "Size", "Analysis": [ { - "Name": "*", + "Name": "gcr.io/gcp-runtimes/diff-base", + "Digest": "sha256:d4e51212be9fffca8d3573a35ac12a69d050c3f65f706e053d24f41317762cca", "Size": 383043168 } ] diff --git a/tests/size_diff_expected.json b/tests/size_diff_expected.json index f1272c4c..cf8fbc5a 100644 --- a/tests/size_diff_expected.json +++ b/tests/size_diff_expected.json @@ -5,7 +5,7 @@ "DiffType": "Size", "Diff": [ { - "Name": "*", + "Name": "", "Size1": 19, "Size2": 15 } diff --git a/tests/size_layer_analysis_expected.json b/tests/size_layer_analysis_expected.json index e03bce36..74729409 100644 --- a/tests/size_layer_analysis_expected.json +++ b/tests/size_layer_analysis_expected.json @@ -5,14 +5,17 @@ "Analysis": [ { "Name": "0", + "Digest": "sha256:74a9fa414ff7212ca555a1024826b94e27ec6a436fd971f8e6c2a909b23630a2", "Size": 6 }, { "Name": "1", + "Digest": "sha256:e39d9b94d5c118b34cce2f4af7efd4c7ed2553e697d7f676e24c4091e42a4998", "Size": 7 }, { "Name": "2", + "Digest": "sha256:bda36409f35ed9e71049d22abdce890d6335056ca15bfed9f323be69b0607e24", "Size": 6 } ] diff --git a/tests/size_layer_diff_expected.json b/tests/size_layer_diff_expected.json index a0f80991..0d6e97a6 100644 --- a/tests/size_layer_diff_expected.json +++ b/tests/size_layer_diff_expected.json @@ -4,11 +4,6 @@ "Image2": "gcr.io/gcp-runtimes/diff-layer-modified", "DiffType": "SizeLayer", "Diff": [ - { - "Name": "0", - "Size1": 6, - "Size2": 6 - }, { "Name": "1", "Size1": 7, diff --git a/util/analyze_output_utils.go b/util/analyze_output_utils.go index ac5ef0d0..ddb4bdf1 100644 --- a/util/analyze_output_utils.go +++ b/util/analyze_output_utils.go @@ -344,9 +344,9 @@ func (r FileLayerAnalyzeResult) OutputText(analyzeType string, format string) er type SizeAnalyzeResult AnalyzeResult func (r SizeAnalyzeResult) OutputStruct() interface{} { - analysis, valid := r.Analysis.([]util.DirectoryEntry) + analysis, valid := r.Analysis.([]SizeEntry) if !valid { - logrus.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry") + logrus.Error("Unexpected structure of Analysis. Should be of type []SizeEntry") return errors.New("Could not output SizeAnalyzer analysis result") } r.Analysis = analysis @@ -354,18 +354,18 @@ func (r SizeAnalyzeResult) OutputStruct() interface{} { } func (r SizeAnalyzeResult) OutputText(analyzeType string, format string) error { - analysis, valid := r.Analysis.([]util.DirectoryEntry) + analysis, valid := r.Analysis.([]SizeEntry) if !valid { - logrus.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry") + logrus.Error("Unexpected structure of Analysis. Should be of type []SizeEntry") return errors.New("Could not output SizeAnalyzer analysis result") } - strAnalysis := stringifyDirectoryEntries(analysis) + strAnalysis := stringifySizeEntries(analysis) strResult := struct { Image string AnalyzeType string - Analysis []StrDirectoryEntry + Analysis []StrSizeEntry }{ Image: r.Image, AnalyzeType: r.AnalyzeType, @@ -373,3 +373,36 @@ func (r SizeAnalyzeResult) OutputText(analyzeType string, format string) error { } return TemplateOutputFromFormat(strResult, "SizeAnalyze", format) } + +type SizeLayerAnalyzeResult AnalyzeResult + +func (r SizeLayerAnalyzeResult) OutputStruct() interface{} { + analysis, valid := r.Analysis.([]SizeEntry) + if !valid { + logrus.Error("Unexpected structure of Analysis. Should be of type []SizeEntry") + return errors.New("Could not output SizeLayerAnalyzer analysis result") + } + r.Analysis = analysis + return r +} + +func (r SizeLayerAnalyzeResult) OutputText(analyzeType string, format string) error { + analysis, valid := r.Analysis.([]SizeEntry) + if !valid { + logrus.Error("Unexpected structure of Analysis. Should be of type []SizeEntry") + return errors.New("Could not output SizeLayerAnalyzer analysis result") + } + + strAnalysis := stringifySizeEntries(analysis) + + strResult := struct { + Image string + AnalyzeType string + Analysis []StrSizeEntry + }{ + Image: r.Image, + AnalyzeType: r.AnalyzeType, + Analysis: strAnalysis, + } + return TemplateOutputFromFormat(strResult, "SizeLayerAnalyze", format) +} diff --git a/util/diff_output_utils.go b/util/diff_output_utils.go index 718dce27..ac65f832 100644 --- a/util/diff_output_utils.go +++ b/util/diff_output_utils.go @@ -300,9 +300,9 @@ func (r DirDiffResult) OutputText(diffType string, format string) error { type SizeDiffResult DiffResult func (r SizeDiffResult) OutputStruct() interface{} { - diff, valid := r.Diff.([]EntryDiff) + diff, valid := r.Diff.([]SizeDiff) if !valid { - logrus.Error("Unexpected structure of Diff. Should be of type []EntryDiff") + logrus.Error("Unexpected structure of Diff. Should be of type []SizeDiff") return errors.New("Could not output SizeAnalyzer diff result") } @@ -311,19 +311,19 @@ func (r SizeDiffResult) OutputStruct() interface{} { } func (r SizeDiffResult) OutputText(diffType string, format string) error { - diff, valid := r.Diff.([]EntryDiff) + diff, valid := r.Diff.([]SizeDiff) if !valid { - logrus.Error("Unexpected structure of Diff. Should be of type []EntryDiff") + logrus.Error("Unexpected structure of Diff. Should be of type []SizeDiff") return errors.New("Could not output SizeAnalyzer diff result") } - strDiff := stringifyEntryDiffs(diff) + strDiff := stringifySizeDiffs(diff) strResult := struct { Image1 string Image2 string DiffType string - Diff []StrEntryDiff + Diff []StrSizeDiff }{ Image1: r.Image1, Image2: r.Image2, @@ -333,6 +333,42 @@ func (r SizeDiffResult) OutputText(diffType string, format string) error { return TemplateOutputFromFormat(strResult, "SizeDiff", format) } +type SizeLayerDiffResult DiffResult + +func (r SizeLayerDiffResult) OutputStruct() interface{} { + diff, valid := r.Diff.([]SizeDiff) + if !valid { + logrus.Error("Unexpected structure of Diff. Should be of type []SizeDiff") + return errors.New("Could not output SizeLayerAnalyzer diff result") + } + + r.Diff = diff + return r +} + +func (r SizeLayerDiffResult) OutputText(diffType string, format string) error { + diff, valid := r.Diff.([]SizeDiff) + if !valid { + logrus.Error("Unexpected structure of Diff. Should be of type []SizeDiff") + return errors.New("Could not output SizeLayerAnalyzer diff result") + } + + strDiff := stringifySizeDiffs(diff) + + strResult := struct { + Image1 string + Image2 string + DiffType string + Diff []StrSizeDiff + }{ + Image1: r.Image1, + Image2: r.Image2, + DiffType: r.DiffType, + Diff: strDiff, + } + return TemplateOutputFromFormat(strResult, "SizeLayerDiff", format) +} + type MultipleDirDiffResult DiffResult func (r MultipleDirDiffResult) OutputStruct() interface{} { diff --git a/util/format_utils.go b/util/format_utils.go index 0b374d30..83717feb 100644 --- a/util/format_utils.go +++ b/util/format_utils.go @@ -40,7 +40,9 @@ var templates = map[string]string{ "FileAnalyze": FileAnalysisOutput, "FileLayerAnalyze": FileLayerAnalysisOutput, "SizeAnalyze": SizeAnalysisOutput, + "SizeLayerAnalyze": SizeLayerAnalysisOutput, "SizeDiff": SizeDiffOutput, + "SizeLayerDiff": SizeLayerDiffOutput, "MultiVersionPackageAnalyze": MultiVersionPackageOutput, "SingleVersionPackageAnalyze": SingleVersionPackageOutput, "SingleVersionPackageLayerAnalyze": SingleVersionPackageLayerOutput, diff --git a/util/output_text_utils.go b/util/output_text_utils.go index 133bffe9..8aeea425 100644 --- a/util/output_text_utils.go +++ b/util/output_text_utils.go @@ -121,3 +121,31 @@ func stringifyEntryDiffs(entries []EntryDiff) (strEntries []StrEntryDiff) { } return } + +type StrSizeEntry struct { + Name string + Digest string + Size string +} + +func stringifySizeEntries(entries []SizeEntry) (strEntries []StrSizeEntry) { + for _, entry := range entries { + strEntry := StrSizeEntry{Name: entry.Name, Digest: entry.Digest.String(), Size: stringifySize(entry.Size)} + strEntries = append(strEntries, strEntry) + } + return +} + +type StrSizeDiff struct { + Name string + Size1 string + Size2 string +} + +func stringifySizeDiffs(entries []SizeDiff) (strEntries []StrSizeDiff) { + for _, entry := range entries { + strEntry := StrSizeDiff{Name: entry.Name, Size1: stringifySize(entry.Size1), Size2: stringifySize(entry.Size2)} + strEntries = append(strEntries, strEntry) + } + return +} diff --git a/util/size_utils.go b/util/size_utils.go new file mode 100644 index 00000000..ad780cb6 --- /dev/null +++ b/util/size_utils.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 Google, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import "github.com/google/go-containerregistry/pkg/v1" + +type SizeEntry struct { + Name string + Digest v1.Hash + Size int64 +} + +type SizeDiff struct { + Name string + Size1 int64 + Size2 int64 +} diff --git a/util/template_utils.go b/util/template_utils.go index 42303a3d..afc4c303 100644 --- a/util/template_utils.go +++ b/util/template_utils.go @@ -103,7 +103,15 @@ const FilenameDiffOutput = ` const SizeDiffOutput = ` -----{{.DiffType}}----- -Size differences between {{.Image1}} and {{.Image2}}:{{if not .Diff}} None{{else}} +Image size difference between {{.Image1}} and {{.Image2}}:{{if not .Diff}} None{{else}} +SIZE1 SIZE2{{range .Diff}}{{"\n"}}{{.Size1}} {{.Size2}}{{end}} +{{end}} +` + +const SizeLayerDiffOutput = ` +-----{{.DiffType}}----- + +Layer size differences between {{.Image1}} and {{.Image2}}:{{if not .Diff}} None{{else}} LAYER SIZE1 SIZE2{{range .Diff}}{{"\n"}}{{.Name}} {{.Size1}} {{.Size2}}{{end}} {{end}} ` @@ -136,7 +144,15 @@ const SizeAnalysisOutput = ` -----{{.AnalyzeType}}----- Analysis for {{.Image}}:{{if not .Analysis}} None{{else}} -LAYER SIZE{{range .Analysis}}{{"\n"}}{{.Name}} {{.Size}}{{end}} +IMAGE DIGEST SIZE{{range .Analysis}}{{"\n"}}{{.Name}} {{.Digest}} {{.Size}}{{end}} +{{end}} +` + +const SizeLayerAnalysisOutput = ` +-----{{.AnalyzeType}}----- + +Analysis for {{.Image}}:{{if not .Analysis}} None{{else}} +LAYER DIGEST SIZE{{range .Analysis}}{{"\n"}}{{.Name}} {{.Digest}} {{.Size}}{{end}} {{end}} ` From 3b35a8c7a3f8e552c901b41ec9b755c5df172c25 Mon Sep 17 00:00:00 2001 From: peter-evans Date: Thu, 27 Sep 2018 10:32:46 +0900 Subject: [PATCH 3/3] Add size analyzer to documentation --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 85cf35f7..7f82e6c5 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Status](https://travis-ci.org/GoogleContainerTools/container-diff.svg?branch=mas container-diff is a tool for analyzing and comparing container images. container-diff can examine images along several different criteria, including: - Docker Image History - Image file system +- Image size - Apt packages - RPM packages - pip packages @@ -45,6 +46,7 @@ To use `container-diff analyze` to perform analysis on a single image, you need container-diff analyze [Run default analyzers] container-diff analyze --type=history [History] container-diff analyze --type=file [File System] +container-diff analyze --type=size [Size] container-diff analyze --type=rpm [RPM] container-diff analyze --type=pip [Pip] container-diff analyze --type=apt [Apt] @@ -60,6 +62,7 @@ To use container-diff to perform a diff analysis on two images, you need two Doc container-diff diff [Run default differs] container-diff diff --type=history [History] container-diff diff --type=file [File System] +container-diff diff --type=size [Size] container-diff diff --type=rpm [RPM] container-diff diff --type=pip [Pip] container-diff diff --type=apt [Apt]