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

Commit 7c0485b

Browse files
authored
Merge pull request #36 from cftorres/SizeSorting
Adding size sorting for package and file differs/analyzers
2 parents 773aca0 + 2744bfe commit 7c0485b

32 files changed

+1350
-491
lines changed

.container-diff-tests.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ while IFS=$' \n\r' read -r flag differ image1 image2 file; do
55
echo "container-diff" "$differ" "differ failed"
66
exit 1
77
fi
8-
done < tests/differ_runs.txt
8+
done < tests/test_differ_runs.txt
99

1010
while IFS=$' \n\r' read -r flag analyzer image file; do
1111
go run main.go $image $flag -j > $file
1212
if [[ $? -ne 0 ]]; then
1313
echo "container-diff" "$analyzer" "analyzer failed"
1414
exit 1
1515
fi
16-
done < tests/analyzer_runs.txt
16+
done < tests/test_analyzer_runs.txt
1717

1818
success=0
1919
while IFS=$' \n\r' read -r type analyzer actual expected; do
@@ -22,7 +22,7 @@ while IFS=$' \n\r' read -r type analyzer actual expected; do
2222
echo "container-diff" "$analyzer" "$type" "output is not as expected"
2323
success=1
2424
fi
25-
done < tests/diff_comparisons.txt
25+
done < tests/test_run_comparisons.txt
2626
if [[ "$success" -ne 0 ]]; then
2727
exit 1
2828
fi

README.md

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ All of the analyzer flags with their long versions can be seen below:
7070

7171
| Differ | Short flag | Long Flag |
7272
| ------------------------- |:----------:| ----------:|
73-
| File System diff | -f | --file |
73+
| File system diff | -f | --file |
7474
| History | -d | --history |
7575
| npm installed packages | -n | --node |
7676
| pip installed packages | -p | --pip |
@@ -89,6 +89,11 @@ To use the docker client instead of shelling out to your local docker daemon, ad
8989

9090
```container-diff <img1> <img2> -e```
9191

92+
To order files and packages by size (in descending order) when performing file system or package analyses/diffs, add a `-o` or `--order` flag.
93+
94+
```container-diff <img1> <img2> -o```
95+
96+
9297
## Analysis Result Format
9398

9499
The JSONs for analysis results are in the following format:
@@ -105,31 +110,34 @@ The possible structures of the `Analysis` field are detailed below.
105110

106111
The history analyzer outputs a list of strings representing descriptions of how an image layer was created.
107112

108-
### Filesystem Analysis
113+
### File System Analysis
109114

110-
The filesystem analyzer outputs a list of strings representing filesystem contents.
115+
The file system analyzer outputs a list of strings representing file system contents.
111116

112117
### Package Analysis
113118

114-
Package analyzers such as pip, apt, and node inspect the packages installed within the image provided. All package analyses leverage the PackageInfo struct, which contains the version and size for a given package instance, as detailed below:
119+
Package analyzers such as pip, apt, and node inspect the packages installed within the image provided. All package analyses leverage the PackageOutput struct, which contains the version and size for a given package instance (and a potential installation path for a specific instance of a package where multiple versions are allowed to be installed), as detailed below:
115120
```
116-
type PackageInfo struct {
121+
type PackageOutput struct {
122+
Name string
123+
Path string
117124
Version string
118-
Size string
125+
Size int64
119126
}
120127
```
121128

122129
#### Single Version Package Analysis
123130

124-
Single version package analyzers (apt) have the following output structure: `map[string]PackageInfo`
131+
Single version package analyzers (apt) have the following output structure: `[]PackageOutput`
132+
133+
Here, the `Path` field is omitted because there is only one instance of each package.
125134

126-
In this mapping scheme, each package name is mapped to its PackageInfo as described above.
127135

128136
#### Multi Version Package Analysis
129137

130-
Multi version package analyzers (pip, node) have the following output structure: `map[string]map[string]PackageInfo`
138+
Multi version package analyzers (pip, node) have the following output structure: `[]PackageOutput`
131139

132-
In this mapping scheme, each package name corresponds to another map where the filesystem path to each unique instance of the package (i.e. unique version and/or size info) is mapped to that package instance's PackageInfo.
140+
Here, the `Path` field is included because there may be more than one instance of each package, and thus the path exists to pinpoint where the package exists in case additional investigation into the package instance is desired.
133141

134142

135143
## Diff Result Format
@@ -156,9 +164,9 @@ type HistDiff struct {
156164
}
157165
```
158166

159-
### Filesystem Diff
167+
### File System Diff
160168

161-
The filesystem differ has the following json output structure:
169+
The file system differ has the following json output structure:
162170

163171
```
164172
type DirDiff struct {
@@ -170,35 +178,41 @@ type DirDiff struct {
170178

171179
### Package Diffs
172180

173-
Package differs such as pip, apt, and node inspect the packages contained within the images provided. All packages differs currently leverage the PackageInfo struct which contains the version and size for a given package instance.
181+
Package differs such as pip, apt, and node inspect the packages contained within the images provided. All packages differs currently leverage the PackageInfo struct which contains the version and size for a given package instance, as detailed below:
182+
```
183+
type PackageInfo struct {
184+
Version string
185+
Size string
186+
}
187+
```
174188

175189
#### Single Version Package Diffs
176190

177191
Single version differs (apt) have the following json output structure:
178192

179193
```
180194
type PackageDiff struct {
181-
Packages1 map[string]PackageInfo
182-
Packages2 map[string]PackageInfo
195+
Packages1 []PackageOutput
196+
Packages2 []PackageOutput
183197
InfoDiff []Info
184198
}
185199
```
186200

187-
Packages1 and Packages2 map package names to PackageInfo structs which contain the version and size of the package. InfoDiff contains a list of Info structs, each of which contains the package name (which occurred in both images but had a difference in size or version), and the PackageInfo struct for each package instance.
201+
Packages1 and Packages2 detail which packages exist uniquely in Image1 and Image2, respectively, with package name, version and size info. InfoDiff contains a list of Info structs, each of which contains the package name (which occurred in both images but had a difference in size or version), and the PackageInfo struct for each package instance.
188202

189203
#### Multi Version Package Diffs
190204

191205
The multi version differs (pip, node) support processing images which may have multiple versions of the same package. Below is the json output structure:
192206

193207
```
194208
type MultiVersionPackageDiff struct {
195-
Packages1 map[string]map[string]PackageInfo
196-
Packages2 map[string]map[string]PackageInfo
209+
Packages1 []PackageOutput
210+
Packages2 []PackageOutput
197211
InfoDiff []MultiVersionInfo
198212
}
199213
```
200214

201-
Packages1 and Packages2 map package name to path where the package was found to PackageInfo struct (version and size of that package instance). InfoDiff here is exanded to allow for multiple versions to be associated with a single package.
215+
Packages1 and Packages2 detail which packages exist uniquely in Image1 and Image2, respectively, with package name, installation path, version and size info. InfoDiff here is exanded to allow for multiple versions to be associated with a single package. In this case, a package of the same name is considered to differ between two images when there exist one or more instances of it installed in one image but not the other (i.e. have a unique version and/or size).
202216

203217
```
204218
type MultiVersionInfo struct {
@@ -228,7 +242,7 @@ Packages found only in gcr.io/google-appengine/python:2017-06-29-190410: None
228242
229243
Version differences:
230244
PACKAGE IMAGE1 (gcr.io/google-appengine/python:2017-07-21-123058) IMAGE2 (gcr.io/google-appengine/python:2017-06-29-190410)
231-
-libgcrypt20 1.6.3-2 deb8u4, 998B 1.6.3-2 deb8u3, 1002B
245+
-libgcrypt20 1.6.3-2 deb8u4, 998K 1.6.3-2 deb8u3, 1002K
232246
233247
-----NodeDiffer-----
234248
@@ -281,12 +295,12 @@ def main():
281295
282296
if len(diff['Packages1']) > 0:
283297
for package in diff['Packages1']:
284-
Size = diff['Packages1'][package]['Size']
298+
Size = package['Size']
285299
img1packages.append((str(package), int(str(Size))))
286300
287301
if len(diff['Packages2']) > 0:
288302
for package in diff['Packages2']:
289-
Size = diff['Packages2'][package]['Size']
303+
Size = package['Size']
290304
img2packages.append((str(package), int(str(Size))))
291305
292306
img1packages = reversed(sorted(img1packages, key=lambda x: x[1]))
@@ -340,21 +354,21 @@ In order to quickly make your own analyzer, follow these steps:
340354
- No: Implement `getPackages` to collect all versions of all packages within an image in a `map[string]PackageInfo`. Use `GetMapDiff` to diff map objects. See [aptDiff.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/aptDiff.go#L29).
341355
- No: Look to [History](https://github.com/GoogleCloudPlatform/container-diff/blob/ReadMe/differs/historyDiff.go) and [File System](https://github.com/GoogleCloudPlatform/container-diff/blob/ReadMe/differs/fileDiff.go) differs as models for diffing.
342356

343-
3. Write your analyzer driver in the `differs` directory, such that you have a struct for your analyzer type and two method for that differ: `Analyze` for single image analysis and `Diff` for comparison between two images:
357+
3. Write your analyzer driver in the `differs` directory, such that you have a struct for your analyzer type and two methods for that analyzer: `Analyze` for single image analysis and `Diff` for comparison between two images:
344358

345359
```
346360
type YourAnalyzer struct {}
347361
348-
func (a YourAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) {...}
349-
func (a YourAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) {...}
362+
func (a YourAnalyzer) Analyze(image utils.Image) (utils.Result, error) {...}
363+
func (a YourAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) {...}
350364
```
351365
The image arguments passed to your analyzer contain the path to the unpacked tar representation of the image, as well as certain configuration information (e.g. environment variables upon image creation and image history).
352366

353-
If using existing package differ tools, you should create the appropriate structs to analyze or diff. Otherwise, create your own analyzer which should yield information to fill an AnalyzeResult or DiffResult in the next step.
367+
If using existing package tools, you should create the appropriate structs (e.g. `SingleVersionPackageAnalyzeResult` or `SingleVersionPackageDiffResult`) to analyze or diff. Otherwise, create your own structs which should yield information to fill an AnalyzeResult or DiffResult as the return type for Analyze() and Diff(), respectively, and should implement the `Result` interface, as in the next step.
354368

355-
4. Create a result struct following either the AnalyzeResult or DiffResult interface by implementing the following two methods.
369+
4. Create a struct following the `Result` interface by implementing the following two methods.
356370
```
357-
GetStruct() DiffResult
371+
GetStruct() interface{}
358372
OutputText(diffType string) error
359373
```
360374

cmd/root.go

Lines changed: 32 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -116,31 +116,8 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error {
116116

117117
req := differs.DiffRequest{image1, image2, diffTypes}
118118
if diffs, err := req.GetDiff(); err == nil {
119-
// Outputs diff results in alphabetical order by differ name
120-
sortedTypes := []string{}
121-
for name := range diffs {
122-
sortedTypes = append(sortedTypes, name)
123-
}
124-
sort.Strings(sortedTypes)
125119
glog.Info("Retrieving diffs")
126-
diffResults := []utils.DiffResult{}
127-
for _, diffType := range sortedTypes {
128-
diff := diffs[diffType]
129-
if json {
130-
diffResults = append(diffResults, diff.GetStruct())
131-
} else {
132-
err = diff.OutputText(diffType)
133-
if err != nil {
134-
glog.Error(err)
135-
}
136-
}
137-
}
138-
if json {
139-
err = utils.JSONify(diffResults)
140-
if err != nil {
141-
glog.Error(err)
142-
}
143-
}
120+
outputResults(diffs)
144121
if !save {
145122
cleanupImage(image1)
146123
cleanupImage(image2)
@@ -175,31 +152,8 @@ func analyzeImage(imageArg string, analyzerArgs []string) error {
175152

176153
req := differs.SingleRequest{image, analyzeTypes}
177154
if analyses, err := req.GetAnalysis(); err == nil {
178-
// Outputs analysis results in alphabetical order by differ name
179-
sortedTypes := []string{}
180-
for name := range analyses {
181-
sortedTypes = append(sortedTypes, name)
182-
}
183-
sort.Strings(sortedTypes)
184-
glog.Info("Retrieving diffs")
185-
analyzeResults := []utils.AnalyzeResult{}
186-
for _, analyzeType := range sortedTypes {
187-
analysis := analyses[analyzeType]
188-
if json {
189-
analyzeResults = append(analyzeResults, analysis.GetStruct())
190-
} else {
191-
err = analysis.OutputText(analyzeType)
192-
if err != nil {
193-
glog.Error(err)
194-
}
195-
}
196-
}
197-
if json {
198-
err = utils.JSONify(analyzeResults)
199-
if err != nil {
200-
glog.Error(err)
201-
}
202-
}
155+
glog.Info("Retrieving analyses")
156+
outputResults(analyses)
203157
if !save {
204158
cleanupImage(image)
205159
} else {
@@ -215,6 +169,34 @@ func analyzeImage(imageArg string, analyzerArgs []string) error {
215169
return nil
216170
}
217171

172+
func outputResults(resultMap map[string]utils.Result) {
173+
// Outputs diff/analysis results in alphabetical order by analyzer name
174+
sortedTypes := []string{}
175+
for analyzerType := range resultMap {
176+
sortedTypes = append(sortedTypes, analyzerType)
177+
}
178+
sort.Strings(sortedTypes)
179+
180+
results := make([]interface{}, len(resultMap))
181+
for i, analyzerType := range sortedTypes {
182+
result := resultMap[analyzerType]
183+
if json {
184+
results[i] = result.OutputStruct()
185+
} else {
186+
err := result.OutputText(analyzerType)
187+
if err != nil {
188+
glog.Error(err)
189+
}
190+
}
191+
}
192+
if json {
193+
err := utils.JSONify(results)
194+
if err != nil {
195+
glog.Error(err)
196+
}
197+
}
198+
}
199+
218200
func cleanupImage(image utils.Image) {
219201
if !reflect.DeepEqual(image, (utils.Image{})) {
220202
glog.Infof("Removing image filesystem directory %s from system", image.FSPath)
@@ -313,4 +295,5 @@ func init() {
313295
RootCmd.Flags().BoolVarP(&file, "file", "f", false, "Set this flag to use the file differ.")
314296
RootCmd.Flags().BoolVarP(&history, "history", "d", false, "Set this flag to use the dockerfile history differ.")
315297
RootCmd.Flags().BoolVarP(&save, "save", "s", false, "Set this flag to save rather than remove the final image filesystems on exit.")
298+
RootCmd.Flags().BoolVarP(&utils.SortSize, "order", "o", false, "Set this flag to sort any file/package results by descending size. Otherwise, they will be sorted by name.")
316299
}

differs/aptDiff.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ type AptAnalyzer struct {
1515
}
1616

1717
// AptDiff compares the packages installed by apt-get.
18-
func (a AptAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) {
18+
func (a AptAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) {
1919
diff, err := singleVersionDiff(image1, image2, a)
2020
return diff, err
2121
}
2222

23-
func (a AptAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) {
23+
func (a AptAnalyzer) Analyze(image utils.Image) (utils.Result, error) {
2424
analysis, err := singleVersionAnalysis(image, a)
2525
return analysis, err
2626
}

differs/differs.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type SingleRequest struct {
2121
}
2222

2323
type Analyzer interface {
24-
Diff(image1, image2 utils.Image) (utils.DiffResult, error)
25-
Analyze(image utils.Image) (utils.AnalyzeResult, error)
24+
Diff(image1, image2 utils.Image) (utils.Result, error)
25+
Analyze(image utils.Image) (utils.Result, error)
2626
}
2727

2828
var analyzers = map[string]Analyzer{
@@ -33,12 +33,12 @@ var analyzers = map[string]Analyzer{
3333
"node": NodeAnalyzer{},
3434
}
3535

36-
func (req DiffRequest) GetDiff() (map[string]utils.DiffResult, error) {
36+
func (req DiffRequest) GetDiff() (map[string]utils.Result, error) {
3737
img1 := req.Image1
3838
img2 := req.Image2
3939
diffs := req.DiffTypes
4040

41-
results := map[string]utils.DiffResult{}
41+
results := map[string]utils.Result{}
4242
for _, differ := range diffs {
4343
differName := reflect.TypeOf(differ).Name()
4444
if diff, err := differ.Diff(img1, img2); err == nil {
@@ -58,11 +58,11 @@ func (req DiffRequest) GetDiff() (map[string]utils.DiffResult, error) {
5858
return results, err
5959
}
6060

61-
func (req SingleRequest) GetAnalysis() (map[string]utils.AnalyzeResult, error) {
61+
func (req SingleRequest) GetAnalysis() (map[string]utils.Result, error) {
6262
img := req.Image
6363
analyses := req.AnalyzeTypes
6464

65-
results := map[string]utils.AnalyzeResult{}
65+
results := map[string]utils.Result{}
6666
for _, analyzer := range analyses {
6767
analyzeName := reflect.TypeOf(analyzer).Name()
6868
if analysis, err := analyzer.Analyze(img); err == nil {

differs/fileDiff.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type FileAnalyzer struct {
88
}
99

1010
// FileDiff diffs two packages and compares their contents
11-
func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) {
11+
func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) {
1212
diff, err := diffImageFiles(image1, image2)
1313
return &utils.DirDiffResult{
1414
Image1: image1.Source,
@@ -18,7 +18,7 @@ func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error)
1818
}, err
1919
}
2020

21-
func (a FileAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) {
21+
func (a FileAnalyzer) Analyze(image utils.Image) (utils.Result, error) {
2222
var result utils.FileAnalyzeResult
2323

2424
imgDir, err := utils.GetDirectory(image.FSPath, true)

0 commit comments

Comments
 (0)