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

Commit a4bd726

Browse files
author
priyawadhwa
authored
Merge pull request #233 from priyawadhwa/layers
Compare layers of images
2 parents e4a55a7 + 0b2a3c5 commit a4bd726

12 files changed

+341
-5
lines changed

cmd/root.go

+34
Original file line numberDiff line numberDiff line change
@@ -167,22 +167,56 @@ func getImageForName(imageName string) (pkgutil.Image, error) {
167167
// TODO(nkubala): implement caching
168168

169169
// create tempdir and extract fs into it
170+
var layers []pkgutil.Layer
171+
if includeLayers() {
172+
imgLayers, err := img.Layers()
173+
if err != nil {
174+
return pkgutil.Image{}, err
175+
}
176+
for _, layer := range imgLayers {
177+
path, err := ioutil.TempDir("", strings.Replace(imageName, "/", "", -1))
178+
if err != nil {
179+
return pkgutil.Image{
180+
Layers: layers,
181+
}, err
182+
}
183+
if err := pkgutil.GetFileSystemForLayer(layer, path, nil); err != nil {
184+
return pkgutil.Image{
185+
Layers: layers,
186+
}, err
187+
}
188+
layers = append(layers, pkgutil.Layer{
189+
FSPath: path,
190+
})
191+
}
192+
}
170193
path, err := ioutil.TempDir("", strings.Replace(imageName, "/", "", -1))
171194
if err != nil {
172195
return pkgutil.Image{}, err
173196
}
174197
if err := pkgutil.GetFileSystemForImage(img, path, nil); err != nil {
175198
return pkgutil.Image{
176199
FSPath: path,
200+
Layers: layers,
177201
}, err
178202
}
179203
return pkgutil.Image{
180204
Image: img,
181205
Source: imageName,
182206
FSPath: path,
207+
Layers: layers,
183208
}, nil
184209
}
185210

211+
func includeLayers() bool {
212+
for _, t := range types {
213+
if t == "layer" {
214+
return true
215+
}
216+
}
217+
return false
218+
}
219+
186220
func init() {
187221
RootCmd.PersistentFlags().StringVarP(&LogLevel, "verbosity", "v", "warning", "This flag controls the verbosity of container-diff.")
188222
RootCmd.PersistentFlags().StringVarP(&format, "format", "", "", "Format to output diff in.")

differs/differs.go

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ var Analyzers = map[string]Analyzer{
4545
"history": HistoryAnalyzer{},
4646
"metadata": MetadataAnalyzer{},
4747
"file": FileAnalyzer{},
48+
"layer": FileLayerAnalyzer{},
4849
"apt": AptAnalyzer{},
4950
"rpm": RPMAnalyzer{},
5051
"pip": PipAnalyzer{},

differs/file_diff.go

+63-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package differs
1919
import (
2020
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
2121
"github.com/GoogleContainerTools/container-diff/util"
22+
"github.com/sirupsen/logrus"
2223
)
2324

2425
type FileAnalyzer struct {
@@ -30,7 +31,7 @@ func (a FileAnalyzer) Name() string {
3031

3132
// FileDiff diffs two packages and compares their contents
3233
func (a FileAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
33-
diff, err := diffImageFiles(image1, image2)
34+
diff, err := diffImageFiles(image1.FSPath, image2.FSPath)
3435
return &util.DirDiffResult{
3536
Image1: image1.Source,
3637
Image2: image2.Source,
@@ -53,10 +54,7 @@ func (a FileAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
5354
return &result, err
5455
}
5556

56-
func diffImageFiles(image1, image2 pkgutil.Image) (util.DirDiff, error) {
57-
img1 := image1.FSPath
58-
img2 := image2.FSPath
59-
57+
func diffImageFiles(img1, img2 string) (util.DirDiff, error) {
6058
var diff util.DirDiff
6159

6260
img1Dir, err := pkgutil.GetDirectory(img1, true)
@@ -71,3 +69,63 @@ func diffImageFiles(image1, image2 pkgutil.Image) (util.DirDiff, error) {
7169
diff, _ = util.DiffDirectory(img1Dir, img2Dir)
7270
return diff, nil
7371
}
72+
73+
type FileLayerAnalyzer struct {
74+
}
75+
76+
func (a FileLayerAnalyzer) Name() string {
77+
return "FileLayerAnalyzer"
78+
}
79+
80+
// FileDiff diffs two packages and compares their contents
81+
func (a FileLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
82+
var dirDiffs []util.DirDiff
83+
84+
// Go through each layer of the first image...
85+
for index, layer := range image1.Layers {
86+
if index >= len(image2.Layers) {
87+
continue
88+
}
89+
// ...else, diff as usual
90+
layer2 := image2.Layers[index]
91+
diff, err := diffImageFiles(layer.FSPath, layer2.FSPath)
92+
if err != nil {
93+
return &util.MultipleDirDiffResult{}, err
94+
}
95+
dirDiffs = append(dirDiffs, diff)
96+
}
97+
98+
// check if there are any additional layers in either image
99+
if len(image1.Layers) != len(image2.Layers) {
100+
if len(image1.Layers) > len(image2.Layers) {
101+
logrus.Infof("%s has additional layers, please use container-diff analyze to view the files in these layers", image1.Source)
102+
} else {
103+
logrus.Infof("%s has additional layers, please use container-diff analyze to view the files in these layers", image2.Source)
104+
}
105+
}
106+
return &util.MultipleDirDiffResult{
107+
Image1: image1.Source,
108+
Image2: image2.Source,
109+
DiffType: "FileLayer",
110+
Diff: util.MultipleDirDiff{
111+
DirDiffs: dirDiffs,
112+
},
113+
}, nil
114+
}
115+
116+
func (a FileLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
117+
var directoryEntries [][]pkgutil.DirectoryEntry
118+
for _, layer := range image.Layers {
119+
layerDir, err := pkgutil.GetDirectory(layer.FSPath, true)
120+
if err != nil {
121+
return util.FileLayerAnalyzeResult{}, err
122+
}
123+
directoryEntries = append(directoryEntries, pkgutil.GetDirectoryEntries(layerDir))
124+
}
125+
126+
return &util.FileLayerAnalyzeResult{
127+
Image: image.Source,
128+
AnalyzeType: "FileLayer",
129+
Analysis: directoryEntries,
130+
}, nil
131+
}

pkg/util/image_utils.go

+21
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,15 @@ import (
3333
"github.com/sirupsen/logrus"
3434
)
3535

36+
type Layer struct {
37+
FSPath string
38+
}
39+
3640
type Image struct {
3741
Image v1.Image
3842
Source string
3943
FSPath string
44+
Layers []Layer
4045
}
4146

4247
type ImageHistoryItem struct {
@@ -50,6 +55,13 @@ func CleanupImage(image Image) {
5055
logrus.Warn(err.Error())
5156
}
5257
}
58+
if image.Layers != nil {
59+
for _, layer := range image.Layers {
60+
if err := os.RemoveAll(layer.FSPath); err != nil {
61+
logrus.Warn(err.Error())
62+
}
63+
}
64+
}
5365
}
5466

5567
func SortMap(m map[string]struct{}) string {
@@ -61,6 +73,15 @@ func SortMap(m map[string]struct{}) string {
6173
return strings.Join(pairs, " ")
6274
}
6375

76+
// GetFileSystemForLayer unpacks a layer to local disk
77+
func GetFileSystemForLayer(layer v1.Layer, root string, whitelist []string) error {
78+
contents, err := layer.Uncompressed()
79+
if err != nil {
80+
return err
81+
}
82+
return unpackTar(tar.NewReader(contents), root, whitelist)
83+
}
84+
6485
// unpack image filesystem to local disk
6586
func GetFileSystemForImage(image v1.Image, root string, whitelist []string) error {
6687
if err := unpackTar(tar.NewReader(mutate.Extract(image)), root, whitelist); err != nil {
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"Image": "gcr.io/gcp-runtimes/diff-layer-base",
4+
"AnalyzeType": "FileLayer",
5+
"Analysis": [
6+
[
7+
{
8+
"Name": "/first",
9+
"Size": 6
10+
}
11+
],
12+
[
13+
{
14+
"Name": "/second",
15+
"Size": 7
16+
}
17+
],
18+
[
19+
{
20+
"Name": "/third",
21+
"Size": 6
22+
}
23+
]
24+
]
25+
}
26+
]

tests/file_layer_diff_expected.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[
2+
{
3+
"Image1": "gcr.io/gcp-runtimes/diff-layer-base",
4+
"Image2": "gcr.io/gcp-runtimes/diff-layer-modified",
5+
"DiffType": "FileLayer",
6+
"Diff": {
7+
"DirDiffs": [
8+
{
9+
"Adds": null,
10+
"Dels": null,
11+
"Mods": null
12+
},
13+
{
14+
"Adds": [
15+
{
16+
"Name": "/modified",
17+
"Size": 9
18+
}
19+
],
20+
"Dels": [
21+
{
22+
"Name": "/second",
23+
"Size": 7
24+
}
25+
],
26+
"Mods": null
27+
}
28+
]
29+
}
30+
}
31+
]

tests/integration_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const (
3838
diffBase = "gcr.io/gcp-runtimes/diff-base"
3939
diffModified = "gcr.io/gcp-runtimes/diff-modified"
4040

41+
diffLayerBase = "gcr.io/gcp-runtimes/diff-layer-base"
42+
diffLayerModifed = "gcr.io/gcp-runtimes/diff-layer-modified"
43+
4144
metadataBase = "gcr.io/gcp-runtimes/metadata-base"
4245
metadataModified = "gcr.io/gcp-runtimes/metadata-modified"
4346

@@ -106,6 +109,14 @@ func TestDiffAndAnalysis(t *testing.T) {
106109
differFlags: []string{"--type=file"},
107110
expectedFile: "file_diff_expected.json",
108111
},
112+
{
113+
description: "file layer differ",
114+
subcommand: "diff",
115+
imageA: diffLayerBase,
116+
imageB: diffLayerModifed,
117+
differFlags: []string{"--type=layer"},
118+
expectedFile: "file_layer_diff_expected.json",
119+
},
109120
{
110121
description: "apt differ",
111122
subcommand: "diff",
@@ -191,6 +202,13 @@ func TestDiffAndAnalysis(t *testing.T) {
191202
differFlags: []string{"--type=file", "-o"},
192203
expectedFile: "file_sorted_analysis_expected.json",
193204
},
205+
{
206+
description: "file layer analysis",
207+
subcommand: "analyze",
208+
imageA: diffLayerBase,
209+
differFlags: []string{"--type=layer"},
210+
expectedFile: "file_layer_analysis_expected.json",
211+
},
194212
{
195213
description: "pip analysis",
196214
subcommand: "analyze",

util/analyze_output_utils.go

+52
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,55 @@ func (r FileAnalyzeResult) OutputText(analyzeType string, format string) error {
216216
}
217217
return TemplateOutputFromFormat(strResult, "FileAnalyze", format)
218218
}
219+
220+
type FileLayerAnalyzeResult AnalyzeResult
221+
222+
func (r FileLayerAnalyzeResult) OutputStruct() interface{} {
223+
analysis, valid := r.Analysis.([][]util.DirectoryEntry)
224+
if !valid {
225+
logrus.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry")
226+
return errors.New("Could not output FileAnalyzer analysis result")
227+
}
228+
229+
for _, a := range analysis {
230+
if SortSize {
231+
directoryBy(directorySizeSort).Sort(a)
232+
} else {
233+
directoryBy(directoryNameSort).Sort(a)
234+
}
235+
}
236+
237+
r.Analysis = analysis
238+
return r
239+
}
240+
241+
func (r FileLayerAnalyzeResult) OutputText(analyzeType string, format string) error {
242+
analysis, valid := r.Analysis.([][]util.DirectoryEntry)
243+
if !valid {
244+
logrus.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry")
245+
return errors.New("Could not output FileAnalyzer analysis result")
246+
}
247+
248+
var strDirectoryEntries [][]StrDirectoryEntry
249+
250+
for _, a := range analysis {
251+
if SortSize {
252+
directoryBy(directorySizeSort).Sort(a)
253+
} else {
254+
directoryBy(directoryNameSort).Sort(a)
255+
}
256+
strAnalysis := stringifyDirectoryEntries(a)
257+
strDirectoryEntries = append(strDirectoryEntries, strAnalysis)
258+
}
259+
260+
strResult := struct {
261+
Image string
262+
AnalyzeType string
263+
Analysis [][]StrDirectoryEntry
264+
}{
265+
Image: r.Image,
266+
AnalyzeType: r.AnalyzeType,
267+
Analysis: strDirectoryEntries,
268+
}
269+
return TemplateOutputFromFormat(strResult, "FileLayerAnalyze", format)
270+
}

0 commit comments

Comments
 (0)