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

Commit ca66892

Browse files
author
Priya Wadhwa
committed
Add --file-layer type to diff layers of images
1 parent 0b210d2 commit ca66892

File tree

10 files changed

+293
-33
lines changed

10 files changed

+293
-33
lines changed

cmd/diff.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,13 @@ import (
3131
)
3232

3333
var filename string
34-
var layers bool
3534

3635
var diffCmd = &cobra.Command{
3736
Use: "diff",
3837
Short: "Compare two images: [image1] [image2]",
3938
Long: `Compares two images using the specifed analyzers as indicated via flags (see documentation for available ones).`,
4039
Args: func(cmd *cobra.Command, args []string) error {
41-
if err := validateArgs(args, checkDiffArgNum, checkIfValidAnalyzer, checkFilenameFlag, checkLayersFlag); err != nil {
40+
if err := validateArgs(args, checkDiffArgNum, checkIfValidAnalyzer, checkFilenameFlag); err != nil {
4241
return err
4342
}
4443
return nil
@@ -70,18 +69,6 @@ func checkFilenameFlag(_ []string) error {
7069
return errors.New("Please include --types=file with the --filename flag")
7170
}
7271

73-
func checkLayersFlag(_ []string) error {
74-
if layers {
75-
for _, t := range types {
76-
if t == "file" {
77-
return nil
78-
}
79-
}
80-
return errors.New("Please include --types=file with the --layers flag")
81-
}
82-
return nil
83-
}
84-
8572
func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
8673
diffTypes, err := differs.GetAnalyzers(diffArgs)
8774
if err != nil {
@@ -115,12 +102,6 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
115102
defer pkgutil.CleanupImage(*imageMap[image2Arg])
116103
}
117104

118-
if layers {
119-
if err := diffLayers(imageMap[image1Arg], imageMap[image2Arg]); err != nil {
120-
return err
121-
}
122-
}
123-
124105
output.PrintToStdErr("Computing diffs\n")
125106
req := differs.DiffRequest{
126107
Image1: *imageMap[image1Arg],
@@ -158,7 +139,6 @@ func diffFile(image1, image2 *pkgutil.Image) error {
158139

159140
func init() {
160141
diffCmd.Flags().StringVarP(&filename, "filename", "f", "", "Set this flag to the path of a file in both containers to view the diff of the file. Must be used with --types=file flag.")
161-
diffCmd.Flags().BoolVarP(&layers, "layers", "", false, "Set this flag to diff the filesystems of two images by layer. Must be used with --types=file flag.")
162142
RootCmd.AddCommand(diffCmd)
163143
addSharedFlags(diffCmd)
164144
output.AddFlags(diffCmd)

cmd/root.go

Lines changed: 34 additions & 0 deletions
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 == "file-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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ type Analyzer interface {
4242
}
4343

4444
var Analyzers = map[string]Analyzer{
45-
"history": HistoryAnalyzer{},
46-
"metadata": MetadataAnalyzer{},
47-
"file": FileAnalyzer{},
48-
"apt": AptAnalyzer{},
49-
"rpm": RPMAnalyzer{},
50-
"pip": PipAnalyzer{},
51-
"node": NodeAnalyzer{},
45+
"history": HistoryAnalyzer{},
46+
"metadata": MetadataAnalyzer{},
47+
"file": FileAnalyzer{},
48+
"file-layer": FileLayerAnalyzer{},
49+
"apt": AptAnalyzer{},
50+
"rpm": RPMAnalyzer{},
51+
"pip": PipAnalyzer{},
52+
"node": NodeAnalyzer{},
5253
}
5354

5455
func (req DiffRequest) GetDiff() (map[string]util.Result, error) {

differs/file_diff.go

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package differs
1919
import (
2020
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
2121
"github.com/GoogleContainerTools/container-diff/util"
22+
"io/ioutil"
23+
"os"
2224
)
2325

2426
type FileAnalyzer struct {
@@ -30,7 +32,7 @@ func (a FileAnalyzer) Name() string {
3032

3133
// FileDiff diffs two packages and compares their contents
3234
func (a FileAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
33-
diff, err := diffImageFiles(image1, image2)
35+
diff, err := diffImageFiles(image1.FSPath, image2.FSPath)
3436
return &util.DirDiffResult{
3537
Image1: image1.Source,
3638
Image2: image2.Source,
@@ -53,10 +55,7 @@ func (a FileAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
5355
return &result, err
5456
}
5557

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

6261
img1Dir, err := pkgutil.GetDirectory(img1, true)
@@ -71,3 +70,81 @@ func diffImageFiles(image1, image2 pkgutil.Image) (util.DirDiff, error) {
7170
diff, _ = util.DiffDirectory(img1Dir, img2Dir)
7271
return diff, nil
7372
}
73+
74+
type FileLayerAnalyzer struct {
75+
}
76+
77+
func (a FileLayerAnalyzer) Name() string {
78+
return "FileLayerAnalyzer"
79+
}
80+
81+
// FileDiff diffs two packages and compares their contents
82+
func (a FileLayerAnalyzer) Diff(image1, image2 pkgutil.Image) (util.Result, error) {
83+
var dirDiffs []util.DirDiff
84+
// This path to an empty dir will be used for diffing in cases
85+
// where one image has more layers than the other
86+
emptyPath, err := ioutil.TempDir("", "")
87+
if err != nil {
88+
return &util.MultipleDirDiffResult{}, err
89+
}
90+
defer os.RemoveAll(emptyPath)
91+
92+
// Go through each layer of the first image...
93+
for index, layer := range image1.Layers {
94+
// ...if there is no corresponding layer in the second image, diff with the empty dir
95+
if index >= len(image2.Layers) {
96+
diff, err := diffImageFiles(layer.FSPath, emptyPath)
97+
if err != nil {
98+
return &util.MultipleDirDiffResult{}, err
99+
}
100+
dirDiffs = append(dirDiffs, diff)
101+
continue
102+
}
103+
// ...else, diff as usual
104+
layer2 := image2.Layers[index]
105+
diff, err := diffImageFiles(layer.FSPath, layer2.FSPath)
106+
if err != nil {
107+
return &util.MultipleDirDiffResult{}, err
108+
}
109+
dirDiffs = append(dirDiffs, diff)
110+
}
111+
112+
// check if there are any additional layers in image2...
113+
if len(image2.Layers) > len(image1.Layers) {
114+
// ... and diff any additional layers with the empty dir
115+
for index := len(image1.Layers); index < len(image2.Layers); index++ {
116+
layer2 := image2.Layers[index]
117+
diff, err := diffImageFiles(emptyPath, layer2.FSPath)
118+
if err != nil {
119+
return &util.MultipleDirDiffResult{}, err
120+
}
121+
dirDiffs = append(dirDiffs, diff)
122+
}
123+
}
124+
return &util.MultipleDirDiffResult{
125+
Image1: image1.Source,
126+
Image2: image2.Source,
127+
DiffType: "FileLayer",
128+
Diff: util.MultipleDirDiff{
129+
DirDiffs: dirDiffs,
130+
},
131+
}, nil
132+
}
133+
134+
func (a FileLayerAnalyzer) Analyze(image pkgutil.Image) (util.Result, error) {
135+
var directoryEntries [][]pkgutil.DirectoryEntry
136+
for _, layer := range image.Layers {
137+
layerDir, err := pkgutil.GetDirectory(layer.FSPath, true)
138+
if err != nil {
139+
return util.FileLayerAnalyzeResult{}, err
140+
}
141+
directoryEntry := pkgutil.GetDirectoryEntries(layerDir)
142+
directoryEntries = append(directoryEntries, directoryEntry)
143+
}
144+
145+
return &util.FileLayerAnalyzeResult{
146+
Image: image.Source,
147+
AnalyzeType: "FileLayer",
148+
Analysis: directoryEntries,
149+
}, nil
150+
}

pkg/util/image_utils.go

Lines changed: 21 additions & 0 deletions
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 {

util/analyze_output_utils.go

Lines changed: 52 additions & 0 deletions
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)