Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.

Commit 8904693

Browse files
authored
Merge pull request #256 from peter-evans/size-analyzer
Add size analyzer
2 parents 78cb059 + 3b35a8c commit 8904693

16 files changed

+492
-16
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Status](https://travis-ci.org/GoogleContainerTools/container-diff.svg?branch=mas
88
container-diff is a tool for analyzing and comparing container images. container-diff can examine images along several different criteria, including:
99
- Docker Image History
1010
- Image file system
11+
- Image size
1112
- Apt packages
1213
- RPM packages
1314
- pip packages
@@ -45,6 +46,7 @@ To use `container-diff analyze` to perform analysis on a single image, you need
4546
container-diff analyze <img> [Run default analyzers]
4647
container-diff analyze <img> --type=history [History]
4748
container-diff analyze <img> --type=file [File System]
49+
container-diff analyze <img> --type=size [Size]
4850
container-diff analyze <img> --type=rpm [RPM]
4951
container-diff analyze <img> --type=pip [Pip]
5052
container-diff analyze <img> --type=apt [Apt]
@@ -60,6 +62,7 @@ To use container-diff to perform a diff analysis on two images, you need two Doc
6062
container-diff diff <img1> <img2> [Run default differs]
6163
container-diff diff <img1> <img2> --type=history [History]
6264
container-diff diff <img1> <img2> --type=file [File System]
65+
container-diff diff <img1> <img2> --type=size [Size]
6366
container-diff diff <img1> <img2> --type=rpm [RPM]
6467
container-diff diff <img1> <img2> --type=pip [Pip]
6568
container-diff diff <img1> <img2> --type=apt [Apt]

cmd/root.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
208208
}
209209
layers = append(layers, pkgutil.Layer{
210210
FSPath: path,
211+
Digest: digest,
211212
})
212213
elapsed := time.Now().Sub(layerStart)
213214
logrus.Infof("time elapsed retrieving layer: %fs", elapsed.Seconds())
@@ -216,7 +217,11 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
216217
logrus.Infof("time elapsed retrieving image layers: %fs", elapsed.Seconds())
217218
}
218219

219-
path, err := getExtractPathForImage(imageName, img)
220+
imageDigest, err := getImageDigest(img)
221+
if err != nil {
222+
return pkgutil.Image{}, err
223+
}
224+
path, err := getExtractPathForName(pkgutil.RemoveTag(imageName) + "@" + imageDigest.String())
220225
if err != nil {
221226
return pkgutil.Image{}, err
222227
}
@@ -231,19 +236,20 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
231236
Image: img,
232237
Source: imageName,
233238
FSPath: path,
239+
Digest: imageDigest,
234240
Layers: layers,
235241
}, nil
236242
}
237243

238-
func getExtractPathForImage(imageName string, image v1.Image) (string, error) {
244+
func getImageDigest(image v1.Image) (digest v1.Hash, err error) {
239245
start := time.Now()
240-
digest, err := image.Digest()
246+
digest, err = image.Digest()
241247
if err != nil {
242-
return "", err
248+
return digest, err
243249
}
244250
elapsed := time.Now().Sub(start)
245251
logrus.Infof("time elapsed retrieving image digest: %fs", elapsed.Seconds())
246-
return getExtractPathForName(pkgutil.RemoveTag(imageName) + "@" + digest.String())
252+
return digest, nil
247253
}
248254

249255
func getExtractPathForName(name string) (string, error) {

differs/differs.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const historyAnalyzer = "history"
2828
const metadataAnalyzer = "metadata"
2929
const fileAnalyzer = "file"
3030
const layerAnalyzer = "layer"
31+
const sizeAnalyzer = "size"
32+
const sizeLayerAnalyzer = "sizelayer"
3133
const aptAnalyzer = "apt"
3234
const aptLayerAnalyzer = "aptlayer"
3335
const rpmAnalyzer = "rpm"
@@ -53,19 +55,21 @@ type Analyzer interface {
5355
}
5456

5557
var Analyzers = map[string]Analyzer{
56-
historyAnalyzer: HistoryAnalyzer{},
57-
metadataAnalyzer: MetadataAnalyzer{},
58-
fileAnalyzer: FileAnalyzer{},
59-
layerAnalyzer: FileLayerAnalyzer{},
60-
aptAnalyzer: AptAnalyzer{},
61-
aptLayerAnalyzer: AptLayerAnalyzer{},
62-
rpmAnalyzer: RPMAnalyzer{},
63-
rpmLayerAnalyzer: RPMLayerAnalyzer{},
64-
pipAnalyzer: PipAnalyzer{},
65-
nodeAnalyzer: NodeAnalyzer{},
58+
historyAnalyzer: HistoryAnalyzer{},
59+
metadataAnalyzer: MetadataAnalyzer{},
60+
fileAnalyzer: FileAnalyzer{},
61+
layerAnalyzer: FileLayerAnalyzer{},
62+
sizeAnalyzer: SizeAnalyzer{},
63+
sizeLayerAnalyzer: SizeLayerAnalyzer{},
64+
aptAnalyzer: AptAnalyzer{},
65+
aptLayerAnalyzer: AptLayerAnalyzer{},
66+
rpmAnalyzer: RPMAnalyzer{},
67+
rpmLayerAnalyzer: RPMLayerAnalyzer{},
68+
pipAnalyzer: PipAnalyzer{},
69+
nodeAnalyzer: NodeAnalyzer{},
6670
}
6771

68-
var LayerAnalyzers = [...]string{layerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer}
72+
var LayerAnalyzers = [...]string{layerAnalyzer, sizeLayerAnalyzer, aptLayerAnalyzer, rpmLayerAnalyzer}
6973

7074
func (req DiffRequest) GetDiff() (map[string]util.Result, error) {
7175
img1 := req.Image1

differs/size_diff.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
Copyright 2018 Google, Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package differs
18+
19+
import (
20+
"strconv"
21+
22+
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
23+
"github.com/GoogleContainerTools/container-diff/util"
24+
)
25+
26+
type SizeAnalyzer struct {
27+
}
28+
29+
func (a SizeAnalyzer) Name() string {
30+
return "SizeAnalyzer"
31+
}
32+
33+
// SizeDiff diffs two images and compares their size
34+
func (a SizeAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
35+
diff := []util.SizeDiff{}
36+
size1 := pkgutil.GetSize(image1.FSPath)
37+
size2 := pkgutil.GetSize(image2.FSPath)
38+
39+
if size1 != size2 {
40+
diff = append(diff, util.SizeDiff{
41+
Size1: size1,
42+
Size2: size2,
43+
})
44+
}
45+
46+
return &util.SizeDiffResult{
47+
Image1: image1.Source,
48+
Image2: image2.Source,
49+
DiffType: "Size",
50+
Diff: diff,
51+
}, nil
52+
}
53+
54+
func (a SizeAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
55+
entries := []util.SizeEntry{
56+
{
57+
Name: image.Source,
58+
Digest: image.Digest,
59+
Size: pkgutil.GetSize(image.FSPath),
60+
},
61+
}
62+
63+
return &util.SizeAnalyzeResult{
64+
Image: image.Source,
65+
AnalyzeType: "Size",
66+
Analysis: entries,
67+
}, nil
68+
}
69+
70+
type SizeLayerAnalyzer struct {
71+
}
72+
73+
func (a SizeLayerAnalyzer) Name() string {
74+
return "SizeLayerAnalyzer"
75+
}
76+
77+
// SizeLayerDiff diffs the layers of two images and compares their size
78+
func (a SizeLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
79+
var layerDiffs []util.SizeDiff
80+
81+
maxLayer := len(image1.Layers)
82+
if len(image2.Layers) > maxLayer {
83+
maxLayer = len(image2.Layers)
84+
}
85+
86+
for index := 0; index < maxLayer; index++ {
87+
var size1, size2 int64 = -1, -1
88+
if index < len(image1.Layers) {
89+
size1 = pkgutil.GetSize(image1.Layers[index].FSPath)
90+
}
91+
if index < len(image2.Layers) {
92+
size2 = pkgutil.GetSize(image2.Layers[index].FSPath)
93+
}
94+
95+
if size1 != size2 {
96+
diff := util.SizeDiff{
97+
Name: strconv.Itoa(index),
98+
Size1: size1,
99+
Size2: size2,
100+
}
101+
layerDiffs = append(layerDiffs, diff)
102+
}
103+
}
104+
105+
return &util.SizeLayerDiffResult{
106+
Image1: image1.Source,
107+
Image2: image2.Source,
108+
DiffType: "SizeLayer",
109+
Diff: layerDiffs,
110+
}, nil
111+
}
112+
113+
func (a SizeLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
114+
var entries []util.SizeEntry
115+
for index, layer := range image.Layers {
116+
entry := util.SizeEntry{
117+
Name: strconv.Itoa(index),
118+
Digest: layer.Digest,
119+
Size: pkgutil.GetSize(layer.FSPath),
120+
}
121+
entries = append(entries, entry)
122+
}
123+
124+
return &util.SizeLayerAnalyzeResult{
125+
Image: image.Source,
126+
AnalyzeType: "SizeLayer",
127+
Analysis: entries,
128+
}, nil
129+
}

pkg/util/image_utils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ const tagRegexStr = ".*:([^/]+$)"
3737

3838
type Layer struct {
3939
FSPath string
40+
Digest v1.Hash
4041
}
4142

4243
type Image struct {
4344
Image v1.Image
4445
Source string
4546
FSPath string
47+
Digest v1.Hash
4648
Layers []Layer
4749
}
4850

tests/integration_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ func TestDiffAndAnalysis(t *testing.T) {
117117
differFlags: []string{"--type=layer", "--no-cache"},
118118
expectedFile: "file_layer_diff_expected.json",
119119
},
120+
{
121+
description: "size differ",
122+
subcommand: "diff",
123+
imageA: diffLayerBase,
124+
imageB: diffLayerModifed,
125+
differFlags: []string{"--type=size", "--no-cache"},
126+
expectedFile: "size_diff_expected.json",
127+
},
128+
{
129+
description: "size layer differ",
130+
subcommand: "diff",
131+
imageA: diffLayerBase,
132+
imageB: diffLayerModifed,
133+
differFlags: []string{"--type=sizelayer", "--no-cache"},
134+
expectedFile: "size_layer_diff_expected.json",
135+
},
120136
{
121137
description: "apt differ",
122138
subcommand: "diff",
@@ -209,6 +225,20 @@ func TestDiffAndAnalysis(t *testing.T) {
209225
differFlags: []string{"--type=layer", "--no-cache"},
210226
expectedFile: "file_layer_analysis_expected.json",
211227
},
228+
{
229+
description: "size analysis",
230+
subcommand: "analyze",
231+
imageA: diffBase,
232+
differFlags: []string{"--type=size", "--no-cache"},
233+
expectedFile: "size_analysis_expected.json",
234+
},
235+
{
236+
description: "size layer analysis",
237+
subcommand: "analyze",
238+
imageA: diffLayerBase,
239+
differFlags: []string{"--type=sizelayer", "--no-cache"},
240+
expectedFile: "size_layer_analysis_expected.json",
241+
},
212242
{
213243
description: "pip analysis",
214244
subcommand: "analyze",

tests/size_analysis_expected.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[
2+
{
3+
"Image": "gcr.io/gcp-runtimes/diff-base",
4+
"AnalyzeType": "Size",
5+
"Analysis": [
6+
{
7+
"Name": "gcr.io/gcp-runtimes/diff-base",
8+
"Digest": "sha256:d4e51212be9fffca8d3573a35ac12a69d050c3f65f706e053d24f41317762cca",
9+
"Size": 383043168
10+
}
11+
]
12+
}
13+
]

tests/size_diff_expected.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"Image1": "gcr.io/gcp-runtimes/diff-layer-base",
4+
"Image2": "gcr.io/gcp-runtimes/diff-layer-modified",
5+
"DiffType": "Size",
6+
"Diff": [
7+
{
8+
"Name": "",
9+
"Size1": 19,
10+
"Size2": 15
11+
}
12+
]
13+
}
14+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[
2+
{
3+
"Image": "gcr.io/gcp-runtimes/diff-layer-base",
4+
"AnalyzeType": "SizeLayer",
5+
"Analysis": [
6+
{
7+
"Name": "0",
8+
"Digest": "sha256:74a9fa414ff7212ca555a1024826b94e27ec6a436fd971f8e6c2a909b23630a2",
9+
"Size": 6
10+
},
11+
{
12+
"Name": "1",
13+
"Digest": "sha256:e39d9b94d5c118b34cce2f4af7efd4c7ed2553e697d7f676e24c4091e42a4998",
14+
"Size": 7
15+
},
16+
{
17+
"Name": "2",
18+
"Digest": "sha256:bda36409f35ed9e71049d22abdce890d6335056ca15bfed9f323be69b0607e24",
19+
"Size": 6
20+
}
21+
]
22+
}
23+
]

tests/size_layer_diff_expected.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[
2+
{
3+
"Image1": "gcr.io/gcp-runtimes/diff-layer-base",
4+
"Image2": "gcr.io/gcp-runtimes/diff-layer-modified",
5+
"DiffType": "SizeLayer",
6+
"Diff": [
7+
{
8+
"Name": "1",
9+
"Size1": 7,
10+
"Size2": 9
11+
},
12+
{
13+
"Name": "2",
14+
"Size1": 6,
15+
"Size2": -1
16+
}
17+
]
18+
}
19+
]

0 commit comments

Comments
 (0)