diff --git a/.container-diff-tests.sh b/.container-diff-tests.sh index 353b69df..1589ea46 100755 --- a/.container-diff-tests.sh +++ b/.container-diff-tests.sh @@ -5,7 +5,7 @@ while IFS=$' \n\r' read -r flag differ image1 image2 file; do echo "container-diff" "$differ" "differ failed" exit 1 fi -done < tests/differ_runs.txt +done < tests/test_differ_runs.txt while IFS=$' \n\r' read -r flag analyzer image file; do go run main.go $image $flag -j > $file @@ -13,7 +13,7 @@ while IFS=$' \n\r' read -r flag analyzer image file; do echo "container-diff" "$analyzer" "analyzer failed" exit 1 fi -done < tests/analyzer_runs.txt +done < tests/test_analyzer_runs.txt success=0 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 echo "container-diff" "$analyzer" "$type" "output is not as expected" success=1 fi -done < tests/diff_comparisons.txt +done < tests/test_run_comparisons.txt if [[ "$success" -ne 0 ]]; then exit 1 fi diff --git a/README.md b/README.md index 77af33fd..2e33259c 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ All of the analyzer flags with their long versions can be seen below: | Differ | Short flag | Long Flag | | ------------------------- |:----------:| ----------:| -| File System diff | -f | --file | +| File system diff | -f | --file | | History | -d | --history | | npm installed packages | -n | --node | | pip installed packages | -p | --pip | @@ -89,6 +89,11 @@ To use the docker client instead of shelling out to your local docker daemon, ad ```container-diff -e``` +To order files and packages by size (in descending order) when performing file system or package analyses/diffs, add a `-o` or `--order` flag. + +```container-diff -o``` + + ## Analysis Result Format The JSONs for analysis results are in the following format: @@ -105,31 +110,34 @@ The possible structures of the `Analysis` field are detailed below. The history analyzer outputs a list of strings representing descriptions of how an image layer was created. -### Filesystem Analysis +### File System Analysis -The filesystem analyzer outputs a list of strings representing filesystem contents. +The file system analyzer outputs a list of strings representing file system contents. ### Package Analysis -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: +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: ``` -type PackageInfo struct { +type PackageOutput struct { + Name string + Path string Version string - Size string + Size int64 } ``` #### Single Version Package Analysis -Single version package analyzers (apt) have the following output structure: `map[string]PackageInfo` +Single version package analyzers (apt) have the following output structure: `[]PackageOutput` + +Here, the `Path` field is omitted because there is only one instance of each package. -In this mapping scheme, each package name is mapped to its PackageInfo as described above. #### Multi Version Package Analysis -Multi version package analyzers (pip, node) have the following output structure: `map[string]map[string]PackageInfo` +Multi version package analyzers (pip, node) have the following output structure: `[]PackageOutput` -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. +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. ## Diff Result Format @@ -156,9 +164,9 @@ type HistDiff struct { } ``` -### Filesystem Diff +### File System Diff -The filesystem differ has the following json output structure: +The file system differ has the following json output structure: ``` type DirDiff struct { @@ -170,7 +178,13 @@ type DirDiff struct { ### Package Diffs -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. +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: +``` +type PackageInfo struct { + Version string + Size string +} +``` #### Single Version Package Diffs @@ -178,13 +192,13 @@ Single version differs (apt) have the following json output structure: ``` type PackageDiff struct { - Packages1 map[string]PackageInfo - Packages2 map[string]PackageInfo + Packages1 []PackageOutput + Packages2 []PackageOutput InfoDiff []Info } ``` -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. +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. #### Multi Version Package Diffs @@ -192,13 +206,13 @@ The multi version differs (pip, node) support processing images which may have m ``` type MultiVersionPackageDiff struct { - Packages1 map[string]map[string]PackageInfo - Packages2 map[string]map[string]PackageInfo + Packages1 []PackageOutput + Packages2 []PackageOutput InfoDiff []MultiVersionInfo } ``` -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. +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). ``` type MultiVersionInfo struct { @@ -228,7 +242,7 @@ Packages found only in gcr.io/google-appengine/python:2017-06-29-190410: None Version differences: PACKAGE IMAGE1 (gcr.io/google-appengine/python:2017-07-21-123058) IMAGE2 (gcr.io/google-appengine/python:2017-06-29-190410) --libgcrypt20 1.6.3-2 deb8u4, 998B 1.6.3-2 deb8u3, 1002B +-libgcrypt20 1.6.3-2 deb8u4, 998K 1.6.3-2 deb8u3, 1002K -----NodeDiffer----- @@ -281,12 +295,12 @@ def main(): if len(diff['Packages1']) > 0: for package in diff['Packages1']: - Size = diff['Packages1'][package]['Size'] + Size = package['Size'] img1packages.append((str(package), int(str(Size)))) if len(diff['Packages2']) > 0: for package in diff['Packages2']: - Size = diff['Packages2'][package]['Size'] + Size = package['Size'] img2packages.append((str(package), int(str(Size)))) img1packages = reversed(sorted(img1packages, key=lambda x: x[1])) @@ -340,21 +354,21 @@ In order to quickly make your own analyzer, follow these steps: - 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). - 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. -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: +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: ``` type YourAnalyzer struct {} -func (a YourAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) {...} -func (a YourAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) {...} +func (a YourAnalyzer) Analyze(image utils.Image) (utils.Result, error) {...} +func (a YourAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) {...} ``` 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). -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. +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. -4. Create a result struct following either the AnalyzeResult or DiffResult interface by implementing the following two methods. +4. Create a struct following the `Result` interface by implementing the following two methods. ``` - GetStruct() DiffResult + GetStruct() interface{} OutputText(diffType string) error ``` diff --git a/cmd/root.go b/cmd/root.go index 66f9fb33..db1e54c5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -116,31 +116,8 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error { req := differs.DiffRequest{image1, image2, diffTypes} if diffs, err := req.GetDiff(); err == nil { - // Outputs diff results in alphabetical order by differ name - sortedTypes := []string{} - for name := range diffs { - sortedTypes = append(sortedTypes, name) - } - sort.Strings(sortedTypes) glog.Info("Retrieving diffs") - diffResults := []utils.DiffResult{} - for _, diffType := range sortedTypes { - diff := diffs[diffType] - if json { - diffResults = append(diffResults, diff.GetStruct()) - } else { - err = diff.OutputText(diffType) - if err != nil { - glog.Error(err) - } - } - } - if json { - err = utils.JSONify(diffResults) - if err != nil { - glog.Error(err) - } - } + outputResults(diffs) if !save { cleanupImage(image1) cleanupImage(image2) @@ -175,31 +152,8 @@ func analyzeImage(imageArg string, analyzerArgs []string) error { req := differs.SingleRequest{image, analyzeTypes} if analyses, err := req.GetAnalysis(); err == nil { - // Outputs analysis results in alphabetical order by differ name - sortedTypes := []string{} - for name := range analyses { - sortedTypes = append(sortedTypes, name) - } - sort.Strings(sortedTypes) - glog.Info("Retrieving diffs") - analyzeResults := []utils.AnalyzeResult{} - for _, analyzeType := range sortedTypes { - analysis := analyses[analyzeType] - if json { - analyzeResults = append(analyzeResults, analysis.GetStruct()) - } else { - err = analysis.OutputText(analyzeType) - if err != nil { - glog.Error(err) - } - } - } - if json { - err = utils.JSONify(analyzeResults) - if err != nil { - glog.Error(err) - } - } + glog.Info("Retrieving analyses") + outputResults(analyses) if !save { cleanupImage(image) } else { @@ -215,6 +169,34 @@ func analyzeImage(imageArg string, analyzerArgs []string) error { return nil } +func outputResults(resultMap map[string]utils.Result) { + // Outputs diff/analysis results in alphabetical order by analyzer name + sortedTypes := []string{} + for analyzerType := range resultMap { + sortedTypes = append(sortedTypes, analyzerType) + } + sort.Strings(sortedTypes) + + results := make([]interface{}, len(resultMap)) + for i, analyzerType := range sortedTypes { + result := resultMap[analyzerType] + if json { + results[i] = result.OutputStruct() + } else { + err := result.OutputText(analyzerType) + if err != nil { + glog.Error(err) + } + } + } + if json { + err := utils.JSONify(results) + if err != nil { + glog.Error(err) + } + } +} + func cleanupImage(image utils.Image) { if !reflect.DeepEqual(image, (utils.Image{})) { glog.Infof("Removing image filesystem directory %s from system", image.FSPath) @@ -313,4 +295,5 @@ func init() { RootCmd.Flags().BoolVarP(&file, "file", "f", false, "Set this flag to use the file differ.") RootCmd.Flags().BoolVarP(&history, "history", "d", false, "Set this flag to use the dockerfile history differ.") RootCmd.Flags().BoolVarP(&save, "save", "s", false, "Set this flag to save rather than remove the final image filesystems on exit.") + 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.") } diff --git a/differs/aptDiff.go b/differs/aptDiff.go index 96ec0c3f..6ca3406d 100644 --- a/differs/aptDiff.go +++ b/differs/aptDiff.go @@ -15,12 +15,12 @@ type AptAnalyzer struct { } // AptDiff compares the packages installed by apt-get. -func (a AptAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a AptAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) { diff, err := singleVersionDiff(image1, image2, a) return diff, err } -func (a AptAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { +func (a AptAnalyzer) Analyze(image utils.Image) (utils.Result, error) { analysis, err := singleVersionAnalysis(image, a) return analysis, err } diff --git a/differs/differs.go b/differs/differs.go index d723e396..976b12b4 100644 --- a/differs/differs.go +++ b/differs/differs.go @@ -21,8 +21,8 @@ type SingleRequest struct { } type Analyzer interface { - Diff(image1, image2 utils.Image) (utils.DiffResult, error) - Analyze(image utils.Image) (utils.AnalyzeResult, error) + Diff(image1, image2 utils.Image) (utils.Result, error) + Analyze(image utils.Image) (utils.Result, error) } var analyzers = map[string]Analyzer{ @@ -33,12 +33,12 @@ var analyzers = map[string]Analyzer{ "node": NodeAnalyzer{}, } -func (req DiffRequest) GetDiff() (map[string]utils.DiffResult, error) { +func (req DiffRequest) GetDiff() (map[string]utils.Result, error) { img1 := req.Image1 img2 := req.Image2 diffs := req.DiffTypes - results := map[string]utils.DiffResult{} + results := map[string]utils.Result{} for _, differ := range diffs { differName := reflect.TypeOf(differ).Name() if diff, err := differ.Diff(img1, img2); err == nil { @@ -58,11 +58,11 @@ func (req DiffRequest) GetDiff() (map[string]utils.DiffResult, error) { return results, err } -func (req SingleRequest) GetAnalysis() (map[string]utils.AnalyzeResult, error) { +func (req SingleRequest) GetAnalysis() (map[string]utils.Result, error) { img := req.Image analyses := req.AnalyzeTypes - results := map[string]utils.AnalyzeResult{} + results := map[string]utils.Result{} for _, analyzer := range analyses { analyzeName := reflect.TypeOf(analyzer).Name() if analysis, err := analyzer.Analyze(img); err == nil { diff --git a/differs/fileDiff.go b/differs/fileDiff.go index f9f2f292..aa835db6 100644 --- a/differs/fileDiff.go +++ b/differs/fileDiff.go @@ -8,7 +8,7 @@ type FileAnalyzer struct { } // FileDiff diffs two packages and compares their contents -func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) { diff, err := diffImageFiles(image1, image2) return &utils.DirDiffResult{ Image1: image1.Source, @@ -18,7 +18,7 @@ func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) }, err } -func (a FileAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { +func (a FileAnalyzer) Analyze(image utils.Image) (utils.Result, error) { var result utils.FileAnalyzeResult imgDir, err := utils.GetDirectory(image.FSPath, true) diff --git a/differs/historyDiff.go b/differs/historyDiff.go index 82c664e0..68a379be 100644 --- a/differs/historyDiff.go +++ b/differs/historyDiff.go @@ -9,7 +9,7 @@ import ( type HistoryAnalyzer struct { } -func (a HistoryAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a HistoryAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) { diff, err := getHistoryDiff(image1, image2) return &utils.HistDiffResult{ Image1: image1.Source, @@ -19,7 +19,7 @@ func (a HistoryAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, err }, err } -func (a HistoryAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { +func (a HistoryAnalyzer) Analyze(image utils.Image) (utils.Result, error) { history := getHistoryList(image.Config.History) result := utils.ListAnalyzeResult{ Image: image.Source, diff --git a/differs/nodeDiff.go b/differs/nodeDiff.go index f069e90f..b1e3ef3d 100644 --- a/differs/nodeDiff.go +++ b/differs/nodeDiff.go @@ -15,12 +15,12 @@ type NodeAnalyzer struct { } // NodeDiff compares the packages installed by apt-get. -func (a NodeAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a NodeAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) { diff, err := multiVersionDiff(image1, image2, a) return diff, err } -func (a NodeAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { +func (a NodeAnalyzer) Analyze(image utils.Image) (utils.Result, error) { analysis, err := multiVersionAnalysis(image, a) return analysis, err } diff --git a/differs/package_differs.go b/differs/package_differs.go index 475b13cf..9d6cde5b 100644 --- a/differs/package_differs.go +++ b/differs/package_differs.go @@ -15,7 +15,7 @@ type SingleVersionPackageAnalyzer interface { getPackages(image utils.Image) (map[string]utils.PackageInfo, error) } -func multiVersionDiff(image1, image2 utils.Image, differ MultiVersionPackageAnalyzer) (utils.DiffResult, error) { +func multiVersionDiff(image1, image2 utils.Image, differ MultiVersionPackageAnalyzer) (*utils.MultiVersionPackageDiffResult, error) { pack1, err := differ.getPackages(image1) if err != nil { return &utils.MultiVersionPackageDiffResult{}, err @@ -34,7 +34,7 @@ func multiVersionDiff(image1, image2 utils.Image, differ MultiVersionPackageAnal }, nil } -func singleVersionDiff(image1, image2 utils.Image, differ SingleVersionPackageAnalyzer) (utils.DiffResult, error) { +func singleVersionDiff(image1, image2 utils.Image, differ SingleVersionPackageAnalyzer) (*utils.SingleVersionPackageDiffResult, error) { pack1, err := differ.getPackages(image1) if err != nil { return &utils.SingleVersionPackageDiffResult{}, err @@ -53,7 +53,7 @@ func singleVersionDiff(image1, image2 utils.Image, differ SingleVersionPackageAn }, nil } -func multiVersionAnalysis(image utils.Image, analyzer MultiVersionPackageAnalyzer) (utils.AnalyzeResult, error) { +func multiVersionAnalysis(image utils.Image, analyzer MultiVersionPackageAnalyzer) (*utils.MultiVersionPackageAnalyzeResult, error) { pack, err := analyzer.getPackages(image) if err != nil { return &utils.MultiVersionPackageAnalyzeResult{}, err @@ -67,7 +67,7 @@ func multiVersionAnalysis(image utils.Image, analyzer MultiVersionPackageAnalyze return &analysis, nil } -func singleVersionAnalysis(image utils.Image, analyzer SingleVersionPackageAnalyzer) (utils.AnalyzeResult, error) { +func singleVersionAnalysis(image utils.Image, analyzer SingleVersionPackageAnalyzer) (*utils.SingleVersionPackageAnalyzeResult, error) { pack, err := analyzer.getPackages(image) if err != nil { return &utils.SingleVersionPackageAnalyzeResult{}, err diff --git a/differs/pipDiff.go b/differs/pipDiff.go index 16cae55c..48f0dd95 100644 --- a/differs/pipDiff.go +++ b/differs/pipDiff.go @@ -15,12 +15,12 @@ type PipAnalyzer struct { } // PipDiff compares pip-installed Python packages between layers of two different images. -func (a PipAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a PipAnalyzer) Diff(image1, image2 utils.Image) (utils.Result, error) { diff, err := multiVersionDiff(image1, image2, a) return diff, err } -func (a PipAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { +func (a PipAnalyzer) Analyze(image utils.Image) (utils.Result, error) { analysis, err := multiVersionAnalysis(image, a) return analysis, err } diff --git a/tests/apt_analysis_expected.json b/tests/apt_analysis_expected.json index 1a5e177e..1e71789f 100644 --- a/tests/apt_analysis_expected.json +++ b/tests/apt_analysis_expected.json @@ -2,407 +2,507 @@ { "Image": "gcr.io/gcp-runtimes/apt-modified", "AnalyzeType": "Apt", - "Analysis": { - "adduser": { + "Analysis": [ + { + "Name": "adduser", "Version": "3.115", "Size": 869376 }, - "apt": { + { + "Name": "apt", "Version": "1.4.6", "Size": 3622912 }, - "base-files": { + { + "Name": "base-files", "Version": "9.9", "Size": 340992 }, - "base-passwd": { + { + "Name": "base-passwd", "Version": "3.5.43", "Size": 234496 }, - "bash": { + { + "Name": "bash", "Version": "4.4-5", "Size": 5937152 }, - "bsdutils": { + { + "Name": "bsdutils", "Version": "1:2.29.2-1", "Size": 243712 }, - "bzip2": { + { + "Name": "bzip2", "Version": "1.0.6-8.1", "Size": 188416 }, - "coreutils": { + { + "Name": "coreutils", "Version": "8.26-3", "Size": 15465472 }, - "dash": { + { + "Name": "dash", "Version": "0.5.8-2.4", "Size": 208896 }, - "debconf": { + { + "Name": "debconf", "Version": "1.5.61", "Size": 571392 }, - "debian-archive-keyring": { + { + "Name": "debian-archive-keyring", "Version": "2017.5", "Size": 120832 }, - "debianutils": { + { + "Name": "debianutils", "Version": "4.8.1.1", "Size": 218112 }, - "diffutils": { + { + "Name": "diffutils", "Version": "1:3.5-3", "Size": 1358848 }, - "dpkg": { + { + "Name": "dpkg", "Version": "1.18.24", "Size": 6906880 }, - "e2fslibs": { + { + "Name": "e2fslibs", "Version": "1.43.4-2", "Size": 459776 }, - "e2fsprogs": { + { + "Name": "e2fsprogs", "Version": "1.43.4-2", "Size": 4118528 }, - "file": { + { + "Name": "file", "Version": "1:5.30-1", "Size": 98304 }, - "findutils": { + { + "Name": "findutils", "Version": "4.6.0 git+20161106-2", "Size": 1898496 }, - "gcc-6-base": { + { + "Name": "gcc-6-base", "Version": "6.3.0-18", "Size": 214016 }, - "gpgv": { + { + "Name": "gpgv", "Version": "2.1.18-6", "Size": 738304 }, - "grep": { + { + "Name": "grep", "Version": "2.27-2", "Size": 1158144 }, - "gzip": { + { + "Name": "gzip", "Version": "1.6-5 b1", "Size": 236544 }, - "hostname": { + { + "Name": "hostname", "Version": "3.18 b1", "Size": 48128 }, - "inetutils-ping": { + { + "Name": "inetutils-ping", "Version": "2:1.9.4-2 b1", "Size": 345088 }, - "init-system-helpers": { + { + "Name": "init-system-helpers", "Version": "1.48", "Size": 134144 }, - "iproute2": { + { + "Name": "iproute2", "Version": "4.9.0-1", "Size": 1795072 }, - "libacl1": { + { + "Name": "libacl1", "Version": "2.2.52-3 b1", "Size": 63488 }, - "libapt-pkg5.0": { + { + "Name": "libapt-pkg5.0", "Version": "1.4.6", "Size": 3128320 }, - "libattr1": { + { + "Name": "libattr1", "Version": "1:2.4.47-2 b2", "Size": 43008 }, - "libaudit-common": { + { + "Name": "libaudit-common", "Version": "1:2.6.7-2", "Size": 30720 }, - "libaudit1": { + { + "Name": "libaudit1", "Version": "1:2.6.7-2", "Size": 153600 }, - "libblkid1": { + { + "Name": "libblkid1", "Version": "2.29.2-1", "Size": 375808 }, - "libbz2-1.0": { + { + "Name": "libbz2-1.0", "Version": "1.0.6-8.1", "Size": 98304 }, - "libc-bin": { + { + "Name": "libc-bin", "Version": "2.24-11 deb9u1", "Size": 3445760 }, - "libc6": { + { + "Name": "libc6", "Version": "2.24-11 deb9u1", "Size": 10940416 }, - "libcap-ng0": { + { + "Name": "libcap-ng0", "Version": "0.7.7-3 b1", "Size": 44032 }, - "libcomerr2": { + { + "Name": "libcomerr2", "Version": "1.43.4-2", "Size": 84992 }, - "libdb5.3": { + { + "Name": "libdb5.3", "Version": "5.3.28-12 b1", "Size": 1858560 }, - "libdebconfclient0": { + { + "Name": "libdebconfclient0", "Version": "0.227", "Size": 68608 }, - "libelf1": { + { + "Name": "libelf1", "Version": "0.168-1", "Size": 956416 }, - "libexpat1": { + { + "Name": "libexpat1", "Version": "2.2.0-2 deb9u1", "Size": 377856 }, - "libfdisk1": { + { + "Name": "libfdisk1", "Version": "2.29.2-1", "Size": 480256 }, - "libffi6": { + { + "Name": "libffi6", "Version": "3.2.1-6", "Size": 57344 }, - "libgcc1": { + { + "Name": "libgcc1", "Version": "1:6.3.0-18", "Size": 110592 }, - "libgcrypt20": { + { + "Name": "libgcrypt20", "Version": "1.7.6-2", "Size": 1292288 }, - "libgpg-error0": { + { + "Name": "libgpg-error0", "Version": "1.26-2", "Size": 585728 }, - "liblz4-1": { + { + "Name": "liblz4-1", "Version": "0.0~r131-2 b1", "Size": 95232 }, - "liblzma5": { + { + "Name": "liblzma5", "Version": "5.2.2-1.2 b1", "Size": 347136 }, - "libmagic-mgc": { + { + "Name": "libmagic-mgc", "Version": "1:5.30-1", "Size": 4947968 }, - "libmagic1": { + { + "Name": "libmagic1", "Version": "1:5.30-1", "Size": 219136 }, - "libmnl0": { + { + "Name": "libmnl0", "Version": "1.0.4-2", "Size": 47104 }, - "libmount1": { + { + "Name": "libmount1", "Version": "2.29.2-1", "Size": 412672 }, - "libncursesw5": { + { + "Name": "libncursesw5", "Version": "6.0 20161126-1", "Size": 355328 }, - "libpam-modules": { + { + "Name": "libpam-modules", "Version": "1.1.8-3.6", "Size": 894976 }, - "libpam-modules-bin": { + { + "Name": "libpam-modules-bin", "Version": "1.1.8-3.6", "Size": 225280 }, - "libpam-runtime": { + { + "Name": "libpam-runtime", "Version": "1.1.8-3.6", "Size": 1040384 }, - "libpam0g": { + { + "Name": "libpam0g", "Version": "1.1.8-3.6", "Size": 234496 }, - "libpcre3": { + { + "Name": "libpcre3", "Version": "2:8.39-3", "Size": 684032 }, - "libpython-stdlib": { + { + "Name": "libpython-stdlib", "Version": "2.7.13-2", "Size": 37888 }, - "libpython2.7-minimal": { + { + "Name": "libpython2.7-minimal", "Version": "2.7.13-2", "Size": 2833408 }, - "libpython2.7-stdlib": { + { + "Name": "libpython2.7-stdlib", "Version": "2.7.13-2", "Size": 8755200 }, - "libreadline7": { + { + "Name": "libreadline7", "Version": "7.0-3", "Size": 425984 }, - "libselinux1": { + { + "Name": "libselinux1", "Version": "2.6-3 b1", "Size": 214016 }, - "libsemanage-common": { + { + "Name": "libsemanage-common", "Version": "2.6-2", "Size": 39936 }, - "libsemanage1": { + { + "Name": "libsemanage1", "Version": "2.6-2", "Size": 297984 }, - "libsepol1": { + { + "Name": "libsepol1", "Version": "2.6-2", "Size": 668672 }, - "libsmartcols1": { + { + "Name": "libsmartcols1", "Version": "2.29.2-1", "Size": 263168 }, - "libsqlite3-0": { + { + "Name": "libsqlite3-0", "Version": "3.16.2-5", "Size": 1189888 }, - "libss2": { + { + "Name": "libss2", "Version": "1.43.4-2", "Size": 97280 }, - "libssl1.1": { + { + "Name": "libssl1.1", "Version": "1.1.0f-3", "Size": 3608576 }, - "libstdc++6": { + { + "Name": "libstdc++6", "Version": "6.3.0-18", "Size": 2045952 }, - "libsystemd0": { + { + "Name": "libsystemd0", "Version": "232-25", "Size": 667648 }, - "libtinfo5": { + { + "Name": "libtinfo5", "Version": "6.0 20161126-1", "Size": 489472 }, - "libudev1": { + { + "Name": "libudev1", "Version": "232-25", "Size": 227328 }, - "libustr-1.0-1": { + { + "Name": "libustr-1.0-1", "Version": "1.0.4-6", "Size": 264192 }, - "libuuid1": { + { + "Name": "libuuid1", "Version": "2.29.2-1", "Size": 109568 }, - "login": { + { + "Name": "login", "Version": "1:4.4-4.1", "Size": 2812928 }, - "lsb-base": { + { + "Name": "lsb-base", "Version": "9.20161125", "Size": 50176 }, - "mawk": { + { + "Name": "mawk", "Version": "1.3.3-17 b3", "Size": 187392 }, - "mime-support": { + { + "Name": "mime-support", "Version": "3.60", "Size": 112640 }, - "mount": { + { + "Name": "mount", "Version": "2.29.2-1", "Size": 454656 }, - "multiarch-support": { + { + "Name": "multiarch-support", "Version": "2.24-11 deb9u1", "Size": 225280 }, - "ncurses-base": { + { + "Name": "ncurses-base", "Version": "6.0 20161126-1", "Size": 348160 }, - "ncurses-bin": { + { + "Name": "ncurses-bin", "Version": "6.0 20161126-1", "Size": 544768 }, - "netbase": { + { + "Name": "netbase", "Version": "5.4", "Size": 45056 }, - "passwd": { + { + "Name": "passwd", "Version": "1:4.4-4.1", "Size": 2537472 }, - "perl-base": { + { + "Name": "perl-base", "Version": "5.24.1-3", "Size": 7719936 }, - "python": { + { + "Name": "python", "Version": "2.7.13-2", "Size": 663552 }, - "python-minimal": { + { + "Name": "python-minimal", "Version": "2.7.13-2", "Size": 148480 }, - "python2.7": { + { + "Name": "python2.7", "Version": "2.7.13-2", "Size": 367616 }, - "python2.7-minimal": { + { + "Name": "python2.7-minimal", "Version": "2.7.13-2", "Size": 3911680 }, - "readline-common": { + { + "Name": "readline-common", "Version": "7.0-3", "Size": 91136 }, - "sed": { + { + "Name": "sed", "Version": "4.4-1", "Size": 818176 }, - "sensible-utils": { + { + "Name": "sensible-utils", "Version": "0.0.9", "Size": 112640 }, - "sysvinit-utils": { + { + "Name": "sysvinit-utils", "Version": "2.88dsf-59.9", "Size": 112640 }, - "tar": { + { + "Name": "tar", "Version": "1.29b-1.1", "Size": 2836480 }, - "tzdata": { + { + "Name": "tzdata", "Version": "2017b-1", "Size": 3082240 }, - "util-linux": { + { + "Name": "util-linux", "Version": "2.29.2-1", "Size": 3643392 }, - "xz-utils": { + { + "Name": "xz-utils", "Version": "5.2.2-1.2 b1", "Size": 528384 }, - "zlib1g": { + { + "Name": "zlib1g", "Version": "1:1.2.8.dfsg-5", "Size": 159744 } - } + ] } ] \ No newline at end of file diff --git a/tests/apt_diff_expected.json b/tests/apt_diff_expected.json index 93e43962..489bb366 100644 --- a/tests/apt_diff_expected.json +++ b/tests/apt_diff_expected.json @@ -4,78 +4,95 @@ "Image2": "gcr.io/gcp-runtimes/apt-modified", "DiffType": "Apt", "Diff": { - "Packages1": { - "dh-python": { + "Packages1": [ + { + "Name": "dh-python", "Version": "2.20170125", "Size": 411648 }, - "libmpdec2": { + { + "Name": "libmpdec2", "Version": "2.4.2-1", "Size": 260096 }, - "libpython3-stdlib": { + { + "Name": "libpython3-stdlib", "Version": "3.5.3-1", "Size": 36864 }, - "libpython3.5-minimal": { + { + "Name": "libpython3.5-minimal", "Version": "3.5.3-1", "Size": 3836928 }, - "libpython3.5-stdlib": { + { + "Name": "libpython3.5-stdlib", "Version": "3.5.3-1", "Size": 10133504 }, - "python3": { + { + "Name": "python3", "Version": "3.5.3-1", "Size": 68608 }, - "python3-minimal": { + { + "Name": "python3-minimal", "Version": "3.5.3-1", "Size": 122880 }, - "python3.5": { + { + "Name": "python3.5", "Version": "3.5.3-1", "Size": 326656 }, - "python3.5-minimal": { + { + "Name": "python3.5-minimal", "Version": "3.5.3-1", "Size": 9636864 } - }, - "Packages2": { - "libffi6": { + ], + "Packages2": [ + { + "Name": "libffi6", "Version": "3.2.1-6", "Size": 57344 }, - "libpython-stdlib": { + { + "Name": "libpython-stdlib", "Version": "2.7.13-2", "Size": 37888 }, - "libpython2.7-minimal": { + { + "Name": "libpython2.7-minimal", "Version": "2.7.13-2", "Size": 2833408 }, - "libpython2.7-stdlib": { + { + "Name": "libpython2.7-stdlib", "Version": "2.7.13-2", "Size": 8755200 }, - "python": { + { + "Name": "python", "Version": "2.7.13-2", "Size": 663552 }, - "python-minimal": { + { + "Name": "python-minimal", "Version": "2.7.13-2", "Size": 148480 }, - "python2.7": { + { + "Name": "python2.7", "Version": "2.7.13-2", "Size": 367616 }, - "python2.7-minimal": { + { + "Name": "python2.7-minimal", "Version": "2.7.13-2", "Size": 3911680 } - }, + ], "InfoDiff": [] } } diff --git a/tests/apt_sorted_diff_expected.json b/tests/apt_sorted_diff_expected.json new file mode 100644 index 00000000..21eeed7a --- /dev/null +++ b/tests/apt_sorted_diff_expected.json @@ -0,0 +1,99 @@ +[ + { + "Image1": "gcr.io/gcp-runtimes/apt-base", + "Image2": "gcr.io/gcp-runtimes/apt-modified", + "DiffType": "Apt", + "Diff": { + "Packages1": [ + { + "Name": "libpython3.5-stdlib", + "Version": "3.5.3-1", + "Size": 10133504 + }, + { + "Name": "python3.5-minimal", + "Version": "3.5.3-1", + "Size": 9636864 + }, + { + "Name": "libpython3.5-minimal", + "Version": "3.5.3-1", + "Size": 3836928 + }, + { + "Name": "dh-python", + "Version": "2.20170125", + "Size": 411648 + }, + { + "Name": "python3.5", + "Version": "3.5.3-1", + "Size": 326656 + }, + { + "Name": "libmpdec2", + "Version": "2.4.2-1", + "Size": 260096 + }, + { + "Name": "python3-minimal", + "Version": "3.5.3-1", + "Size": 122880 + }, + { + "Name": "python3", + "Version": "3.5.3-1", + "Size": 68608 + }, + { + "Name": "libpython3-stdlib", + "Version": "3.5.3-1", + "Size": 36864 + } + ], + "Packages2": [ + { + "Name": "libpython2.7-stdlib", + "Version": "2.7.13-2", + "Size": 8755200 + }, + { + "Name": "python2.7-minimal", + "Version": "2.7.13-2", + "Size": 3911680 + }, + { + "Name": "libpython2.7-minimal", + "Version": "2.7.13-2", + "Size": 2833408 + }, + { + "Name": "python", + "Version": "2.7.13-2", + "Size": 663552 + }, + { + "Name": "python2.7", + "Version": "2.7.13-2", + "Size": 367616 + }, + { + "Name": "python-minimal", + "Version": "2.7.13-2", + "Size": 148480 + }, + { + "Name": "libffi6", + "Version": "3.2.1-6", + "Size": 57344 + }, + { + "Name": "libpython-stdlib", + "Version": "2.7.13-2", + "Size": 37888 + } + ], + "InfoDiff": [] + } + } +] \ No newline at end of file diff --git a/tests/file_sorted_analysis_expected.json b/tests/file_sorted_analysis_expected.json new file mode 100644 index 00000000..ac69d7e0 --- /dev/null +++ b/tests/file_sorted_analysis_expected.json @@ -0,0 +1,80 @@ +[ + { + "Image": "gcr.io/gcp-runtimes/diff-modified", + "AnalyzeType": "File", + "Analysis": [ + { + "Name": "/bin", + "Size": 1105296 + }, + { + "Name": "/bin/[", + "Size": 1026712 + }, + { + "Name": "/bin/getconf", + "Size": 78584 + }, + { + "Name": "/etc", + "Size": 1008 + }, + { + "Name": "/etc/passwd", + "Size": 340 + }, + { + "Name": "/etc/group", + "Size": 307 + }, + { + "Name": "/etc/shadow", + "Size": 243 + }, + { + "Name": "/etc/localtime", + "Size": 118 + }, + { + "Name": "/dev", + "Size": 0 + }, + { + "Name": "/home", + "Size": 0 + }, + { + "Name": "/root", + "Size": 0 + }, + { + "Name": "/tmp", + "Size": 0 + }, + { + "Name": "/usr", + "Size": 0 + }, + { + "Name": "/usr/sbin", + "Size": 0 + }, + { + "Name": "/var", + "Size": 0 + }, + { + "Name": "/var/spool", + "Size": 0 + }, + { + "Name": "/var/spool/mail", + "Size": 0 + }, + { + "Name": "/var/www", + "Size": 0 + } + ] + } +] \ No newline at end of file diff --git a/tests/multi_diff_expected.json b/tests/multi_diff_expected.json index bca1d42d..c9b33909 100644 --- a/tests/multi_diff_expected.json +++ b/tests/multi_diff_expected.json @@ -4,45 +4,54 @@ "Image2": "gcr.io/gcp-runtimes/multi-modified", "DiffType": "Apt", "Diff": { - "Packages1": {}, - "Packages2": { - "dh-python": { + "Packages1": [], + "Packages2": [ + { + "Name": "dh-python", "Version": "1.20141111-2", "Size": 283648 }, - "libmpdec2": { + { + "Name": "libmpdec2", "Version": "2.4.1-1", "Size": 281600 }, - "libpython3-stdlib": { + { + "Name": "libpython3-stdlib", "Version": "3.4.2-2", "Size": 28672 }, - "libpython3.4-minimal": { + { + "Name": "libpython3.4-minimal", "Version": "3.4.2-1", "Size": 3389440 }, - "libpython3.4-stdlib": { + { + "Name": "libpython3.4-stdlib", "Version": "3.4.2-1", "Size": 9711616 }, - "python3": { + { + "Name": "python3", "Version": "3.4.2-2", "Size": 36864 }, - "python3-minimal": { + { + "Name": "python3-minimal", "Version": "3.4.2-2", "Size": 98304 }, - "python3.4": { + { + "Name": "python3.4", "Version": "3.4.2-1", "Size": 344064 }, - "python3.4-minimal": { + { + "Name": "python3.4-minimal", "Version": "3.4.2-1", "Size": 4614144 } - }, + ], "InfoDiff": [] } }, @@ -51,15 +60,15 @@ "Image2": "gcr.io/gcp-runtimes/multi-modified", "DiffType": "Node", "Diff": { - "Packages1": {}, - "Packages2": { - "pax": { - "/node_modules/pax/": { - "Version": "0.2.1", - "Size": 11998 - } + "Packages1": [], + "Packages2": [ + { + "Name": "pax", + "Path": "/node_modules/pax/", + "Version": "0.2.1", + "Size": 11998 } - }, + ], "InfoDiff": [ { "Package": "sax", @@ -84,22 +93,22 @@ "Image2": "gcr.io/gcp-runtimes/multi-modified", "DiffType": "Pip", "Diff": { - "Packages1": { - "pbr": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "3.1.1", - "Size": 447110 - } + "Packages1": [ + { + "Name": "pbr", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "3.1.1", + "Size": 447110 } - }, - "Packages2": { - "retrying": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "1.3.3", - "Size": 9955 - } + ], + "Packages2": [ + { + "Name": "retrying", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "1.3.3", + "Size": 9955 } - }, + ], "InfoDiff": [ { "Package": "mock", diff --git a/tests/node_analysis_expected.json b/tests/node_analysis_expected.json index 3860dd4e..70d94ef0 100644 --- a/tests/node_analysis_expected.json +++ b/tests/node_analysis_expected.json @@ -2,25 +2,25 @@ { "Image": "gcr.io/gcp-runtimes/node-modified", "AnalyzeType": "Node", - "Analysis": { - "npm": { - "/usr/local/lib/node_modules/npm/": { - "Version": "5.0.3", - "Size": 13830218 - } + "Analysis": [ + { + "Name": "npm", + "Path": "/usr/local/lib/node_modules/npm/", + "Version": "5.0.3", + "Size": 13830218 }, - "pax": { - "/node_modules/pax/": { - "Version": "0.2.1", - "Size": 11365 - } + { + "Name": "pax", + "Path": "/node_modules/pax/", + "Version": "0.2.1", + "Size": 11365 }, - "sax": { - "/node_modules/sax/": { - "Version": "0.1.1", - "Size": 127390 - } + { + "Name": "sax", + "Path": "/node_modules/sax/", + "Version": "0.1.1", + "Size": 127390 } - } + ] } ] \ No newline at end of file diff --git a/tests/node_diff_order_expected.json b/tests/node_diff_order_expected.json index e24fa53b..e8a08836 100644 --- a/tests/node_diff_order_expected.json +++ b/tests/node_diff_order_expected.json @@ -4,15 +4,15 @@ "Image2": "gcr.io/gcp-runtimes/node-modified", "DiffType": "Node", "Diff": { - "Packages1": {}, - "Packages2": { - "pax": { - "/node_modules/pax/": { - "Version": "0.2.1", - "Size": 11365 - } + "Packages1": [], + "Packages2": [ + { + "Name": "pax", + "Path": "/node_modules/pax/", + "Version": "0.2.1", + "Size": 11365 } - }, + ], "InfoDiff": [] } } diff --git a/tests/pip_analysis_expected.json b/tests/pip_analysis_expected.json index 72c46e5f..5234c8e7 100644 --- a/tests/pip_analysis_expected.json +++ b/tests/pip_analysis_expected.json @@ -2,43 +2,43 @@ { "Image": "gcr.io/gcp-runtimes/pip-modified", "AnalyzeType": "Pip", - "Analysis": { - "mock": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "2.0.0", - "Size": 504226 - } + "Analysis": [ + { + "Name": "mock", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "2.0.0", + "Size": 504226 }, - "pbr": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "3.1.1", - "Size": 447110 - } + { + "Name": "pbr", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "3.1.1", + "Size": 447110 }, - "pip": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "9.0.1", - "Size": 5289421 - } + { + "Name": "pip", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "9.0.1", + "Size": 5289421 }, - "setuptools": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "36.0.1", - "Size": 837337 - } + { + "Name": "setuptools", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "36.0.1", + "Size": 837337 }, - "six": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "1.10.0", - "Size": 30098 - } + { + "Name": "six", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "1.10.0", + "Size": 30098 }, - "wheel": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "0.29.0", - "Size": 103509 - } + { + "Name": "wheel", + "Path": "/usr/local/lib/python3.6/site-packages", + "Version": "0.29.0", + "Size": 103509 } - } + ] } ] \ No newline at end of file diff --git a/tests/analyzer_runs.txt b/tests/test_analyzer_runs.txt similarity index 71% rename from tests/analyzer_runs.txt rename to tests/test_analyzer_runs.txt index 7f8ffb75..43615d0b 100644 --- a/tests/analyzer_runs.txt +++ b/tests/test_analyzer_runs.txt @@ -1,3 +1,4 @@ -p pip gcr.io/gcp-runtimes/pip-modified tests/pip_analysis_actual.json -a apt gcr.io/gcp-runtimes/apt-modified tests/apt_analysis_actual.json -n node gcr.io/gcp-runtimes/node-modified tests/node_analysis_actual.json +-fo fileOrder gcr.io/gcp-runtimes/diff-modified tests/file_sorted_analysis_actual.json diff --git a/tests/differ_runs.txt b/tests/test_differ_runs.txt similarity index 82% rename from tests/differ_runs.txt rename to tests/test_differ_runs.txt index 86840f9c..5b83c7e4 100644 --- a/tests/differ_runs.txt +++ b/tests/test_differ_runs.txt @@ -3,3 +3,4 @@ -n nodeOrder gcr.io/gcp-runtimes/node-modified:2.0 gcr.io/gcp-runtimes/node-modified tests/node_diff_order_actual.json -npa multi gcr.io/gcp-runtimes/multi-base gcr.io/gcp-runtimes/multi-modified tests/multi_diff_actual.json -d history gcr.io/gcp-runtimes/diff-base gcr.io/gcp-runtimes/diff-modified tests/hist_diff_actual.json +-ao aptSort gcr.io/gcp-runtimes/apt-base gcr.io/gcp-runtimes/apt-modified tests/apt_sorted_diff_actual.json diff --git a/tests/diff_comparisons.txt b/tests/test_run_comparisons.txt similarity index 76% rename from tests/diff_comparisons.txt rename to tests/test_run_comparisons.txt index c06884f4..8764707c 100644 --- a/tests/diff_comparisons.txt +++ b/tests/test_run_comparisons.txt @@ -6,3 +6,5 @@ history diff tests/hist_diff_actual.json tests/hist_diff_expected.json pip analysis tests/pip_analysis_actual.json tests/pip_analysis_expected.json apt analysis tests/apt_analysis_actual.json tests/apt_analysis_expected.json node analysis tests/node_analysis_actual.json tests/node_analysis_expected.json +aptSort diff tests/apt_sorted_diff_actual.json tests/apt_sorted_diff_expected.json +fileSort analyis tests/file_sorted_analysis_actual.json tests/file_sorted_analysis_expected.json diff --git a/utils/analyze_output_utils.go b/utils/analyze_output_utils.go index b74847f2..631951cc 100644 --- a/utils/analyze_output_utils.go +++ b/utils/analyze_output_utils.go @@ -1,46 +1,74 @@ package utils import ( - "code.cloudfoundry.org/bytefmt" + "errors" + "fmt" + + "github.com/golang/glog" ) -type AnalyzeResult interface { - GetStruct() AnalyzeResult - OutputText(analyzeType string) error +type Result interface { + OutputStruct() interface{} + OutputText(resultType string) error } -type ListAnalyzeResult struct { +type AnalyzeResult struct { Image string AnalyzeType string - Analysis []string + Analysis interface{} } -func (r ListAnalyzeResult) GetStruct() AnalyzeResult { +type ListAnalyzeResult AnalyzeResult + +func (r ListAnalyzeResult) OutputStruct() interface{} { return r } -func (r ListAnalyzeResult) OutputText(analyzeType string) error { +func (r ListAnalyzeResult) OutputText(resultType string) error { + analysis, valid := r.Analysis.([]string) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type []string") + return errors.New(fmt.Sprintf("Could not output %s analysis result", r.AnalyzeType)) + } + r.Analysis = analysis + return TemplateOutput(r, "ListAnalyze") } -type MultiVersionPackageAnalyzeResult struct { - Image string - AnalyzeType string - Analysis map[string]map[string]PackageInfo -} +type MultiVersionPackageAnalyzeResult AnalyzeResult -func (r MultiVersionPackageAnalyzeResult) GetStruct() AnalyzeResult { - return r +func (r MultiVersionPackageAnalyzeResult) OutputStruct() interface{} { + analysis, valid := r.Analysis.(map[string]map[string]PackageInfo) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type map[string]map[string]PackageInfo") + return errors.New(fmt.Sprintf("Could not output %s analysis result", r.AnalyzeType)) + } + analysisOutput := getMultiVersionPackageOutput(analysis) + output := struct { + Image string + AnalyzeType string + Analysis []PackageOutput + }{ + Image: r.Image, + AnalyzeType: r.AnalyzeType, + Analysis: analysisOutput, + } + return output } -func (r MultiVersionPackageAnalyzeResult) OutputText(analyzeType string) error { - analysis := r.Analysis - strAnalysis := stringifyMultiVersionPackages(analysis) +func (r MultiVersionPackageAnalyzeResult) OutputText(resultType string) error { + analysis, valid := r.Analysis.(map[string]map[string]PackageInfo) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type map[string]map[string]PackageInfo") + return errors.New(fmt.Sprintf("Could not output %s analysis result", r.AnalyzeType)) + } + analysisOutput := getMultiVersionPackageOutput(analysis) + strAnalysis := stringifyPackages(analysisOutput) strResult := struct { Image string AnalyzeType string - Analysis map[string]map[string]StrPackageInfo + Analysis []StrPackageOutput }{ Image: r.Image, AnalyzeType: r.AnalyzeType, @@ -49,32 +77,40 @@ func (r MultiVersionPackageAnalyzeResult) OutputText(analyzeType string) error { return TemplateOutput(strResult, "MultiVersionPackageAnalyze") } -func stringifyMultiVersionPackages(packages map[string]map[string]PackageInfo) map[string]map[string]StrPackageInfo { - strPackages := map[string]map[string]StrPackageInfo{} - for pack, versionMap := range packages { - strPackages[pack] = stringifyPackages(versionMap) - } - return strPackages -} - -type SingleVersionPackageAnalyzeResult struct { - Image string - AnalyzeType string - Analysis map[string]PackageInfo -} +type SingleVersionPackageAnalyzeResult AnalyzeResult -func (r SingleVersionPackageAnalyzeResult) GetStruct() AnalyzeResult { - return r +func (r SingleVersionPackageAnalyzeResult) OutputStruct() interface{} { + analysis, valid := r.Analysis.(map[string]PackageInfo) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type map[string]PackageInfo") + return errors.New(fmt.Sprintf("Could not output %s analysis result", r.AnalyzeType)) + } + analysisOutput := getSingleVersionPackageOutput(analysis) + output := struct { + Image string + AnalyzeType string + Analysis []PackageOutput + }{ + Image: r.Image, + AnalyzeType: r.AnalyzeType, + Analysis: analysisOutput, + } + return output } func (r SingleVersionPackageAnalyzeResult) OutputText(diffType string) error { - analysis := r.Analysis - strAnalysis := stringifyPackages(analysis) + analysis, valid := r.Analysis.(map[string]PackageInfo) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type map[string]PackageInfo") + return errors.New(fmt.Sprintf("Could not output %s analysis result", r.AnalyzeType)) + } + analysisOutput := getSingleVersionPackageOutput(analysis) + strAnalysis := stringifyPackages(analysisOutput) strResult := struct { Image string AnalyzeType string - Analysis map[string]StrPackageInfo + Analysis []StrPackageOutput }{ Image: r.Image, AnalyzeType: r.AnalyzeType, @@ -83,44 +119,73 @@ func (r SingleVersionPackageAnalyzeResult) OutputText(diffType string) error { return TemplateOutput(strResult, "SingleVersionPackageAnalyze") } -type StrPackageInfo struct { +type PackageOutput struct { + Name string + Path string `json:",omitempty"` Version string - Size string + Size int64 } -func stringifyPackageInfo(info PackageInfo) StrPackageInfo { - return StrPackageInfo{Version: info.Version, Size: stringifySize(info.Size)} -} +func getSingleVersionPackageOutput(packageMap map[string]PackageInfo) []PackageOutput { + packages := []PackageOutput{} + for name, info := range packageMap { + packages = append(packages, PackageOutput{Name: name, Version: info.Version, Size: info.Size}) + } -func stringifySize(size int64) string { - strSize := "unknown" - if size != -1 { - strSize = bytefmt.ByteSize(uint64(size)) + if SortSize { + packageBy(packageSizeSort).Sort(packages) + } else { + packageBy(packageNameSort).Sort(packages) } - return strSize + return packages } -func stringifyPackages(packages map[string]PackageInfo) map[string]StrPackageInfo { - strPackages := map[string]StrPackageInfo{} - for pack, info := range packages { - strInfo := stringifyPackageInfo(info) - strPackages[pack] = strInfo +func getMultiVersionPackageOutput(packageMap map[string]map[string]PackageInfo) []PackageOutput { + packages := []PackageOutput{} + for name, versionMap := range packageMap { + for path, info := range versionMap { + packages = append(packages, PackageOutput{Name: name, Path: path, Version: info.Version, Size: info.Size}) + } } - return strPackages -} -type FileAnalyzeResult struct { - Image string - AnalyzeType string - Analysis []DirectoryEntry + if SortSize { + packageBy(packageSizeSort).Sort(packages) + } else { + packageBy(packageNameSort).Sort(packages) + } + return packages } -func (r FileAnalyzeResult) GetStruct() AnalyzeResult { +type FileAnalyzeResult AnalyzeResult + +func (r FileAnalyzeResult) OutputStruct() interface{} { + analysis, valid := r.Analysis.([]DirectoryEntry) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry") + return errors.New("Could not output FileAnalyzer analysis result") + } + + if SortSize { + directoryBy(directorySizeSort).Sort(analysis) + } else { + directoryBy(directoryNameSort).Sort(analysis) + } + r.Analysis = analysis return r } func (r FileAnalyzeResult) OutputText(analyzeType string) error { - analysis := r.Analysis + analysis, valid := r.Analysis.([]DirectoryEntry) + if !valid { + glog.Error("Unexpected structure of Analysis. Should be of type []DirectoryEntry") + return errors.New("Could not output FileAnalyzer analysis result") + } + + if SortSize { + directoryBy(directorySizeSort).Sort(analysis) + } else { + directoryBy(directoryNameSort).Sort(analysis) + } strAnalysis := stringifyDirectoryEntries(analysis) strResult := struct { @@ -134,16 +199,3 @@ func (r FileAnalyzeResult) OutputText(analyzeType string) error { } return TemplateOutput(strResult, "FileAnalyze") } - -type StrDirectoryEntry struct { - Name string - Size string -} - -func stringifyDirectoryEntries(entries []DirectoryEntry) (strEntries []StrDirectoryEntry) { - for _, entry := range entries { - strEntry := StrDirectoryEntry{Name: entry.Name, Size: stringifySize(entry.Size)} - strEntries = append(strEntries, strEntry) - } - return -} diff --git a/utils/diff_output_utils.go b/utils/diff_output_utils.go index 174fbb34..64a7e091 100644 --- a/utils/diff_output_utils.go +++ b/utils/diff_output_utils.go @@ -1,31 +1,55 @@ package utils -type DiffResult interface { - GetStruct() DiffResult - OutputText(diffType string) error -} +import ( + "errors" + "fmt" + + "github.com/golang/glog" +) -type MultiVersionPackageDiffResult struct { +type DiffResult struct { Image1 string Image2 string DiffType string - Diff MultiVersionPackageDiff + Diff interface{} } -func (r MultiVersionPackageDiffResult) GetStruct() DiffResult { +type MultiVersionPackageDiffResult DiffResult + +func (r MultiVersionPackageDiffResult) OutputStruct() interface{} { + diff, valid := r.Diff.(MultiVersionPackageDiff) + if !valid { + glog.Error("Unexpected structure of Diff. Should follow the MultiVersionPackageDiff struct") + return errors.New(fmt.Sprintf("Could not output %s diff result", r.DiffType)) + } + + diffOutput := struct { + Packages1 []PackageOutput + Packages2 []PackageOutput + InfoDiff []MultiVersionInfo + }{ + Packages1: getMultiVersionPackageOutput(diff.Packages1), + Packages2: getMultiVersionPackageOutput(diff.Packages2), + InfoDiff: getMultiVersionInfoDiffOutput(diff.InfoDiff), + } + r.Diff = diffOutput return r } func (r MultiVersionPackageDiffResult) OutputText(diffType string) error { - diff := r.Diff + diff, valid := r.Diff.(MultiVersionPackageDiff) + if !valid { + glog.Error("Unexpected structure of Diff. Should follow the MultiVersionPackageDiff struct") + return errors.New(fmt.Sprintf("Could not output %s diff result", r.DiffType)) + } - strPackages1 := stringifyMultiVersionPackages(diff.Packages1) - strPackages2 := stringifyMultiVersionPackages(diff.Packages2) - strInfoDiff := stringifyMultiVersionPackageDiff(diff.InfoDiff) + strPackages1 := stringifyPackages(getMultiVersionPackageOutput(diff.Packages1)) + strPackages2 := stringifyPackages(getMultiVersionPackageOutput(diff.Packages2)) + strInfoDiff := stringifyMultiVersionPackageDiff(getMultiVersionInfoDiffOutput(diff.InfoDiff)) type StrDiff struct { - Packages1 map[string]map[string]StrPackageInfo - Packages2 map[string]map[string]StrPackageInfo + Packages1 []StrPackageOutput + Packages2 []StrPackageOutput InfoDiff []StrMultiVersionInfo } @@ -47,51 +71,51 @@ func (r MultiVersionPackageDiffResult) OutputText(diffType string) error { return TemplateOutput(strResult, "MultiVersionPackageDiff") } -type StrMultiVersionInfo struct { - Package string - Info1 []StrPackageInfo - Info2 []StrPackageInfo +func getMultiVersionInfoDiffOutput(infoDiff []MultiVersionInfo) []MultiVersionInfo { + if SortSize { + multiInfoBy(multiInfoSizeSort).Sort(infoDiff) + } else { + multiInfoBy(multiInfoNameSort).Sort(infoDiff) + } + return infoDiff } -func stringifyMultiVersionPackageDiff(infoDiff []MultiVersionInfo) (strInfoDiff []StrMultiVersionInfo) { - for _, diff := range infoDiff { - strInfos1 := []StrPackageInfo{} - for _, info := range diff.Info1 { - strInfos1 = append(strInfos1, stringifyPackageInfo(info)) - } - - strInfos2 := []StrPackageInfo{} - for _, info := range diff.Info2 { - strInfos2 = append(strInfos2, stringifyPackageInfo(info)) - } +type SingleVersionPackageDiffResult DiffResult - strDiff := StrMultiVersionInfo{Package: diff.Package, Info1: strInfos1, Info2: strInfos2} - strInfoDiff = append(strInfoDiff, strDiff) +func (r SingleVersionPackageDiffResult) OutputStruct() interface{} { + diff, valid := r.Diff.(PackageDiff) + if !valid { + glog.Error("Unexpected structure of Diff. Should follow the PackageDiff struct") + return errors.New(fmt.Sprintf("Could not output %s diff result", r.DiffType)) } - return -} -type SingleVersionPackageDiffResult struct { - Image1 string - Image2 string - DiffType string - Diff PackageDiff -} - -func (r SingleVersionPackageDiffResult) GetStruct() DiffResult { + diffOutput := struct { + Packages1 []PackageOutput + Packages2 []PackageOutput + InfoDiff []Info + }{ + Packages1: getSingleVersionPackageOutput(diff.Packages1), + Packages2: getSingleVersionPackageOutput(diff.Packages2), + InfoDiff: getSingleVersionInfoDiffOutput(diff.InfoDiff), + } + r.Diff = diffOutput return r } func (r SingleVersionPackageDiffResult) OutputText(diffType string) error { - diff := r.Diff + diff, valid := r.Diff.(PackageDiff) + if !valid { + glog.Error("Unexpected structure of Diff. Should follow the PackageDiff struct") + return errors.New(fmt.Sprintf("Could not output %s diff result", r.DiffType)) + } - strPackages1 := stringifyPackages(diff.Packages1) - strPackages2 := stringifyPackages(diff.Packages2) - strInfoDiff := stringifyPackageDiff(diff.InfoDiff) + strPackages1 := stringifyPackages(getSingleVersionPackageOutput(diff.Packages1)) + strPackages2 := stringifyPackages(getSingleVersionPackageOutput(diff.Packages2)) + strInfoDiff := stringifyPackageDiff(getSingleVersionInfoDiffOutput(diff.InfoDiff)) type StrDiff struct { - Packages1 map[string]StrPackageInfo - Packages2 map[string]StrPackageInfo + Packages1 []StrPackageOutput + Packages2 []StrPackageOutput InfoDiff []StrInfo } @@ -113,31 +137,18 @@ func (r SingleVersionPackageDiffResult) OutputText(diffType string) error { return TemplateOutput(strResult, "SingleVersionPackageDiff") } -type StrInfo struct { - Package string - Info1 StrPackageInfo - Info2 StrPackageInfo -} - -func stringifyPackageDiff(infoDiff []Info) (strInfoDiff []StrInfo) { - for _, diff := range infoDiff { - strInfo1 := stringifyPackageInfo(diff.Info1) - strInfo2 := stringifyPackageInfo(diff.Info2) - - strDiff := StrInfo{Package: diff.Package, Info1: strInfo1, Info2: strInfo2} - strInfoDiff = append(strInfoDiff, strDiff) +func getSingleVersionInfoDiffOutput(infoDiff []Info) []Info { + if SortSize { + singleInfoBy(singleInfoSizeSort).Sort(infoDiff) + } else { + singleInfoBy(singleInfoNameSort).Sort(infoDiff) } - return + return infoDiff } -type HistDiffResult struct { - Image1 string - Image2 string - DiffType string - Diff HistDiff -} +type HistDiffResult DiffResult -func (r HistDiffResult) GetStruct() DiffResult { +func (r HistDiffResult) OutputStruct() interface{} { return r } @@ -145,19 +156,26 @@ func (r HistDiffResult) OutputText(diffType string) error { return TemplateOutput(r, "HistDiff") } -type DirDiffResult struct { - Image1 string - Image2 string - DiffType string - Diff DirDiff -} +type DirDiffResult DiffResult + +func (r DirDiffResult) OutputStruct() interface{} { + diff, valid := r.Diff.(DirDiff) + if !valid { + glog.Error("Unexpected structure of Diff. Should follow the DirDiff struct") + return errors.New("Could not output FileAnalyzer diff result") + } -func (r DirDiffResult) GetStruct() DiffResult { + r.Diff = sortDirDiff(diff) return r } func (r DirDiffResult) OutputText(diffType string) error { - diff := r.Diff + diff, valid := r.Diff.(DirDiff) + if !valid { + glog.Error("Unexpected structure of Diff. Should follow the DirDiff struct") + return errors.New("Could not output FileAnalyzer diff result") + } + diff = sortDirDiff(diff) strAdds := stringifyDirectoryEntries(diff.Adds) strDels := stringifyDirectoryEntries(diff.Dels) @@ -186,17 +204,3 @@ func (r DirDiffResult) OutputText(diffType string) error { } return TemplateOutput(strResult, "DirDiff") } - -type StrEntryDiff struct { - Name string - Size1 string - Size2 string -} - -func stringifyEntryDiffs(entries []EntryDiff) (strEntries []StrEntryDiff) { - for _, entry := range entries { - strEntry := StrEntryDiff{Name: entry.Name, Size1: stringifySize(entry.Size1), Size2: stringifySize(entry.Size2)} - strEntries = append(strEntries, strEntry) - } - return -} diff --git a/utils/output_sort_utils.go b/utils/output_sort_utils.go new file mode 100644 index 00000000..c8746ee5 --- /dev/null +++ b/utils/output_sort_utils.go @@ -0,0 +1,259 @@ +package utils + +import ( + "sort" +) + +var SortSize bool + +type packageBy func(p1, p2 *PackageOutput) bool + +func (by packageBy) Sort(packages []PackageOutput) { + ps := &packageSorter{ + packages: packages, + by: by, + } + sort.Sort(ps) +} + +type packageSorter struct { + packages []PackageOutput + by func(p1, p2 *PackageOutput) bool +} + +func (s *packageSorter) Len() int { + return len(s.packages) +} + +func (s *packageSorter) Less(i, j int) bool { + return s.by(&s.packages[i], &s.packages[j]) +} + +func (s *packageSorter) Swap(i, j int) { + s.packages[i], s.packages[j] = s.packages[j], s.packages[i] +} + +// If packages have the same name, means they exist where multiple version of the same package are allowed, +// so sort by version. If they have the same version, then sort by size. +var packageNameSort = func(p1, p2 *PackageOutput) bool { + if p1.Name == p2.Name { + if p1.Version == p2.Version { + return p1.Size > p2.Size + } + return p1.Version < p2.Version + } + return p1.Name < p2.Name +} + +// If packages have the same size, sort by name. If they are two versions of the same package, sort by version. +var packageSizeSort = func(p1, p2 *PackageOutput) bool { + if p1.Size == p2.Size { + if p1.Name == p2.Name { + return p1.Version < p2.Version + } + return p1.Name < p2.Name + } + return p1.Size > p2.Size +} + +type singleInfoBy func(a, b *Info) bool + +func (by singleInfoBy) Sort(packageDiffs []Info) { + ss := &singleVersionInfoSorter{ + packageDiffs: packageDiffs, + by: by, + } + sort.Sort(ss) +} + +type singleVersionInfoSorter struct { + packageDiffs []Info + by func(a, b *Info) bool +} + +func (s *singleVersionInfoSorter) Len() int { + return len(s.packageDiffs) +} + +func (s *singleVersionInfoSorter) Less(i, j int) bool { + return s.by(&s.packageDiffs[i], &s.packageDiffs[j]) +} + +func (s *singleVersionInfoSorter) Swap(i, j int) { + s.packageDiffs[i], s.packageDiffs[j] = s.packageDiffs[j], s.packageDiffs[i] +} + +var singleInfoNameSort = func(a, b *Info) bool { + return a.Package < b.Package +} + +// Sorts MultiVersionInfos by package instance with the largest size in the first image, in descending order +var singleInfoSizeSort = func(a, b *Info) bool { + return a.Info1.Size > b.Info1.Size +} + +type multiInfoBy func(a, b *MultiVersionInfo) bool + +func (by multiInfoBy) Sort(packageDiffs []MultiVersionInfo) { + ms := &multiVersionInfoSorter{ + packageDiffs: packageDiffs, + by: by, + } + sort.Sort(ms) +} + +type multiVersionInfoSorter struct { + packageDiffs []MultiVersionInfo + by func(a, b *MultiVersionInfo) bool +} + +func (s *multiVersionInfoSorter) Len() int { + return len(s.packageDiffs) +} + +func (s *multiVersionInfoSorter) Less(i, j int) bool { + return s.by(&s.packageDiffs[i], &s.packageDiffs[j]) +} + +func (s *multiVersionInfoSorter) Swap(i, j int) { + s.packageDiffs[i], s.packageDiffs[j] = s.packageDiffs[j], s.packageDiffs[i] +} + +var multiInfoNameSort = func(a, b *MultiVersionInfo) bool { + return a.Package < b.Package +} + +// Sorts MultiVersionInfos by package instance with the largest size in the first image, in descending order +var multiInfoSizeSort = func(a, b *MultiVersionInfo) bool { + aInfo1 := a.Info1 + bInfo1 := b.Info1 + + // For each package, sorts the infos of the first image's instances of that package in descending order + sort.Sort(packageInfoBySize(aInfo1)) + sort.Sort(packageInfoBySize(bInfo1)) + // Compares the largest size instances of each package in the first image + return aInfo1[0].Size > bInfo1[0].Size +} + +type packageInfoBySize []PackageInfo + +func (infos packageInfoBySize) Len() int { + return len(infos) +} + +func (infos packageInfoBySize) Swap(i, j int) { + infos[i], infos[j] = infos[j], infos[i] +} + +func (infos packageInfoBySize) Less(i, j int) bool { + if infos[i].Size == infos[j].Size { + return infos[i].Version < infos[j].Version + } + return infos[i].Size > infos[j].Size +} + +type packageInfoByVersion []PackageInfo + +func (infos packageInfoByVersion) Len() int { + return len(infos) +} + +func (infos packageInfoByVersion) Swap(i, j int) { + infos[i], infos[j] = infos[j], infos[i] +} + +func (infos packageInfoByVersion) Less(i, j int) bool { + if infos[i].Version == infos[j].Version { + return infos[i].Size > infos[j].Size + } + return infos[i].Version < infos[j].Version +} + +type directoryBy func(e1, e2 *DirectoryEntry) bool + +func (by directoryBy) Sort(entries []DirectoryEntry) { + ds := &directorySorter{ + entries: entries, + by: by, + } + sort.Sort(ds) +} + +type directorySorter struct { + entries []DirectoryEntry + by func(p1, p2 *DirectoryEntry) bool +} + +func (s *directorySorter) Len() int { + return len(s.entries) +} + +func (s *directorySorter) Less(i, j int) bool { + return s.by(&s.entries[i], &s.entries[j]) +} + +func (s *directorySorter) Swap(i, j int) { + s.entries[i], s.entries[j] = s.entries[j], s.entries[i] +} + +var directoryNameSort = func(e1, e2 *DirectoryEntry) bool { + return e1.Name < e2.Name +} + +// If directory entries have the same size, sort by name. +var directorySizeSort = func(e1, e2 *DirectoryEntry) bool { + if e1.Size == e2.Size { + return e1.Name < e2.Name + } + return e1.Size > e2.Size +} + +func sortDirDiff(diff DirDiff) DirDiff { + adds, dels, mods := diff.Adds, diff.Dels, diff.Mods + if SortSize { + directoryBy(directorySizeSort).Sort(adds) + directoryBy(directorySizeSort).Sort(dels) + entryDiffBy(entryDiffSizeSort).Sort(mods) + } else { + directoryBy(directoryNameSort).Sort(adds) + directoryBy(directoryNameSort).Sort(dels) + entryDiffBy(entryDiffSizeSort).Sort(mods) + } + return DirDiff{adds, dels, mods} +} + +type entryDiffBy func(a, b *EntryDiff) bool + +func (by entryDiffBy) Sort(entryDiffs []EntryDiff) { + ds := &entryDiffSorter{ + entryDiffs: entryDiffs, + by: by, + } + sort.Sort(ds) +} + +type entryDiffSorter struct { + entryDiffs []EntryDiff + by func(a, b *EntryDiff) bool +} + +func (s *entryDiffSorter) Len() int { + return len(s.entryDiffs) +} + +func (s *entryDiffSorter) Less(i, j int) bool { + return s.by(&s.entryDiffs[i], &s.entryDiffs[j]) +} + +func (s *entryDiffSorter) Swap(i, j int) { + s.entryDiffs[i], s.entryDiffs[j] = s.entryDiffs[j], s.entryDiffs[i] +} + +var entryDiffNameSort = func(a, b *EntryDiff) bool { + return a.Name < b.Name +} + +// Sorts by size of the files in the first image, in descending order +var entryDiffSizeSort = func(a, b *EntryDiff) bool { + return a.Size1 > b.Size1 +} diff --git a/utils/output_sort_utils_test.go b/utils/output_sort_utils_test.go new file mode 100644 index 00000000..b3083378 --- /dev/null +++ b/utils/output_sort_utils_test.go @@ -0,0 +1,130 @@ +package utils + +import ( + "reflect" + "testing" +) + +var packageTests = [][]PackageOutput{ + { + {Name: "a", Version: "1.2", Size: 10}, + {Name: "b", Version: "1.5", Size: 12}, + {Name: "c", Version: "1.4", Size: 20}, + }, + { + {Name: "a", Version: "1.2", Size: 10}, + {Name: "b", Version: "1.5", Size: 12}, + {Name: "c", Version: "1.4", Size: 12}, + }, + { + {Name: "a", Version: "1.2", Size: 10}, + {Name: "a", Version: "1.4", Size: 20}, + {Name: "a", Version: "1.2", Size: 15}, + }, +} + +func TestSortPackageOutput(t *testing.T) { + for _, test := range []struct { + input []PackageOutput + sortBy func(a, b *PackageOutput) bool + expected []PackageOutput + }{ + { + input: packageTests[0], + sortBy: packageSizeSort, + expected: []PackageOutput{ + {Name: "c", Version: "1.4", Size: 20}, + {Name: "b", Version: "1.5", Size: 12}, + {Name: "a", Version: "1.2", Size: 10}, + }, + }, + { + input: packageTests[0], + sortBy: packageNameSort, + expected: []PackageOutput{ + {Name: "a", Version: "1.2", Size: 10}, + {Name: "b", Version: "1.5", Size: 12}, + {Name: "c", Version: "1.4", Size: 20}, + }, + }, + { + input: packageTests[1], + sortBy: packageSizeSort, + expected: []PackageOutput{ + {Name: "b", Version: "1.5", Size: 12}, + {Name: "c", Version: "1.4", Size: 12}, + {Name: "a", Version: "1.2", Size: 10}, + }, + }, + { + input: packageTests[2], + sortBy: packageNameSort, + expected: []PackageOutput{ + {Name: "a", Version: "1.2", Size: 15}, + {Name: "a", Version: "1.2", Size: 10}, + {Name: "a", Version: "1.4", Size: 20}, + }, + }, + } { + actual := test.input + packageBy(test.sortBy).Sort(actual) + if !reflect.DeepEqual(actual, test.expected) { + t.Errorf("\nExpected: %v\nGot: %v", test.expected, actual) + } + } +} + +var directoryTests = [][]DirectoryEntry{ + { + {Name: "a", Size: 10}, + {Name: "b", Size: 12}, + {Name: "c", Size: 20}, + }, + { + {Name: "a", Size: 10}, + {Name: "b", Size: 12}, + {Name: "c", Size: 12}, + }, +} + +func TestSortDirectoryEntries(t *testing.T) { + for _, test := range []struct { + input []DirectoryEntry + sortBy func(a, b *DirectoryEntry) bool + expected []DirectoryEntry + }{ + { + input: directoryTests[0], + sortBy: directorySizeSort, + expected: []DirectoryEntry{ + {Name: "c", Size: 20}, + {Name: "b", Size: 12}, + {Name: "a", Size: 10}, + }, + }, + { + input: directoryTests[0], + sortBy: directoryNameSort, + expected: []DirectoryEntry{ + {Name: "a", Size: 10}, + {Name: "b", Size: 12}, + {Name: "c", Size: 20}, + }, + }, + { + input: directoryTests[1], + sortBy: directorySizeSort, + expected: []DirectoryEntry{ + {Name: "b", Size: 12}, + {Name: "c", Size: 12}, + {Name: "a", Size: 10}, + }, + }, + } { + actual := test.input + directoryBy(test.sortBy).Sort(actual) + if !reflect.DeepEqual(actual, test.expected) { + t.Errorf("\nExpected: %v\nGot: %v", test.expected, actual) + } + } +} diff --git a/utils/output_text_utils.go b/utils/output_text_utils.go new file mode 100644 index 00000000..41e4328b --- /dev/null +++ b/utils/output_text_utils.go @@ -0,0 +1,106 @@ +package utils + +import ( + "code.cloudfoundry.org/bytefmt" +) + +type StrPackageOutput struct { + Name string + Path string + Version string + Size string +} + +func stringifySize(size int64) string { + strSize := "unknown" + if size != -1 { + strSize = bytefmt.ByteSize(uint64(size)) + } + return strSize +} + +func stringifyPackages(packages []PackageOutput) []StrPackageOutput { + strPackages := []StrPackageOutput{} + for _, pack := range packages { + strSize := stringifySize(pack.Size) + strPackages = append(strPackages, StrPackageOutput{pack.Name, pack.Path, pack.Version, strSize}) + } + return strPackages +} + +type StrMultiVersionInfo struct { + Package string + Info1 []StrPackageInfo + Info2 []StrPackageInfo +} + +type StrPackageInfo struct { + Version string + Size string +} + +func stringifyPackageInfo(info PackageInfo) StrPackageInfo { + return StrPackageInfo{Version: info.Version, Size: stringifySize(info.Size)} +} + +type StrInfo struct { + Package string + Info1 StrPackageInfo + Info2 StrPackageInfo +} + +func stringifyPackageDiff(infoDiff []Info) (strInfoDiff []StrInfo) { + for _, diff := range infoDiff { + strInfo1 := stringifyPackageInfo(diff.Info1) + strInfo2 := stringifyPackageInfo(diff.Info2) + + strDiff := StrInfo{Package: diff.Package, Info1: strInfo1, Info2: strInfo2} + strInfoDiff = append(strInfoDiff, strDiff) + } + return +} + +func stringifyMultiVersionPackageDiff(infoDiff []MultiVersionInfo) (strInfoDiff []StrMultiVersionInfo) { + for _, diff := range infoDiff { + strInfos1 := []StrPackageInfo{} + for _, info := range diff.Info1 { + strInfos1 = append(strInfos1, stringifyPackageInfo(info)) + } + + strInfos2 := []StrPackageInfo{} + for _, info := range diff.Info2 { + strInfos2 = append(strInfos2, stringifyPackageInfo(info)) + } + + strDiff := StrMultiVersionInfo{Package: diff.Package, Info1: strInfos1, Info2: strInfos2} + strInfoDiff = append(strInfoDiff, strDiff) + } + return +} + +type StrDirectoryEntry struct { + Name string + Size string +} + +func stringifyDirectoryEntries(entries []DirectoryEntry) (strEntries []StrDirectoryEntry) { + for _, entry := range entries { + strEntry := StrDirectoryEntry{Name: entry.Name, Size: stringifySize(entry.Size)} + strEntries = append(strEntries, strEntry) + } + return +} + +type StrEntryDiff struct { + Name string + Size1 string + Size2 string +} + +func stringifyEntryDiffs(entries []EntryDiff) (strEntries []StrEntryDiff) { + for _, entry := range entries { + strEntry := StrEntryDiff{Name: entry.Name, Size1: stringifySize(entry.Size1), Size2: stringifySize(entry.Size2)} + strEntries = append(strEntries, strEntry) + } + return +} diff --git a/utils/tar_utils.go b/utils/tar_utils.go index 948ddd2b..844c1af9 100644 --- a/utils/tar_utils.go +++ b/utils/tar_utils.go @@ -18,7 +18,7 @@ func unpackTar(tr *tar.Reader, path string) error { break } if err != nil { - glog.Fatalf(err.Error()) + glog.Error("Error getting next tar header") return err } @@ -44,6 +44,7 @@ func unpackTar(tr *tar.Reader, path string) error { case tar.TypeDir: if _, err := os.Stat(target); err != nil { if err := os.MkdirAll(target, mode); err != nil { + glog.Errorf("Error creating directory %s while untarring", target) return err } continue @@ -54,6 +55,7 @@ func unpackTar(tr *tar.Reader, path string) error { currFile, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode) if err != nil { + glog.Errorf("Error opening file %s", target) return err } _, err = io.Copy(currFile, tr) diff --git a/utils/template_utils.go b/utils/template_utils.go index 193756ca..ca2d6742 100644 --- a/utils/template_utils.go +++ b/utils/template_utils.go @@ -4,12 +4,10 @@ const FSDiffOutput = ` -----{{.DiffType}}----- These entries have been added to {{.Image1}}:{{if not .Diff.Adds}} None{{else}} -FILE SIZE{{range .Diff.Adds}}{{"\n"}}{{.Name}} {{.Size}}{{end}} -{{end}} +FILE SIZE{{range .Diff.Adds}}{{"\n"}}{{.Name}} {{.Size}}{{end}}{{end}} These entries have been deleted from {{.Image1}}:{{if not .Diff.Dels}} None{{else}} -FILE SIZE{{range .Diff.Dels}}{{"\n"}}{{.Name}} {{.Size}}{{end}} -{{end}} +FILE SIZE{{range .Diff.Dels}}{{"\n"}}{{.Name}} {{.Size}}{{end}}{{end}} These entries have been changed between {{.Image1}} and {{.Image2}}:{{if not .Diff.Mods}} None{{else}} FILE SIZE1 SIZE2{{range .Diff.Mods}}{{"\n"}}{{.Name}} {{.Size1}} {{.Size2}}{{end}} @@ -20,10 +18,10 @@ const SingleVersionDiffOutput = ` -----{{.DiffType}}----- Packages found only in {{.Image1}}:{{if not .Diff.Packages1}} None{{else}} -NAME VERSION SIZE{{range $name, $value := .Diff.Packages1}}{{"\n"}}{{print "-"}}{{$name}} {{$value.Version}} {{$value.Size}}{{end}}{{end}} +NAME VERSION SIZE{{range .Diff.Packages1}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}{{end}} Packages found only in {{.Image2}}:{{if not .Diff.Packages2}} None{{else}} -NAME VERSION SIZE{{range $name, $value := .Diff.Packages2}}{{"\n"}}{{print "-"}}{{$name}} {{$value.Version}} {{$value.Size}}{{end}}{{end}} +NAME VERSION SIZE{{range .Diff.Packages2}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}{{end}} Version differences:{{if not .Diff.InfoDiff}} None{{else}} PACKAGE IMAGE1 ({{.Image1}}) IMAGE2 ({{.Image2}}){{range .Diff.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{.Info1.Version}}, {{.Info1.Size}} {{.Info2.Version}}, {{.Info2.Size}}{{end}} @@ -34,10 +32,10 @@ const MultiVersionDiffOutput = ` -----{{.DiffType}}----- Packages found only in {{.Image1}}:{{if not .Diff.Packages1}} None{{else}} -NAME VERSION SIZE{{range $name, $value := .Diff.Packages1}}{{"\n"}}{{print "-"}}{{$name}} {{range $key, $info := $value}}{{$info.Version}} {{$info.Size}}{{end}}{{end}}{{end}} +NAME VERSION SIZE{{range .Diff.Packages1}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}{{end}} Packages found only in {{.Image2}}:{{if not .Diff.Packages2}} None{{else}} -NAME VERSION SIZE{{range $name, $value := .Diff.Packages2}}{{"\n"}}{{print "-"}}{{$name}} {{range $key, $info := $value}}{{$info.Version}} {{$info.Size}}{{end}}{{end}}{{end}} +NAME VERSION SIZE{{range .Diff.Packages2}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}}{{end}} Version differences:{{if not .Diff.InfoDiff}} None{{else}} PACKAGE IMAGE1 ({{.Image1}}) IMAGE2 ({{.Image2}}){{range .Diff.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{range .Info1}}{{.Version}}, {{.Size}}{{end}} {{range .Info2}}{{.Version}}, {{.Size}}{{end}}{{end}} @@ -70,12 +68,14 @@ const MultiVersionPackageOutput = ` -----{{.AnalyzeType}}----- Packages found in {{.Image}}:{{if not .Analysis}} None{{else}} -NAME VERSION SIZE{{range $name, $value := .Analysis}}{{"\n"}}{{print "-"}}{{$name}} {{range $key, $info := $value}}{{$info.Version}} {{$info.Size}}{{end}}{{end}}{{end}} +NAME VERSION SIZE INSTALLATION{{range .Analysis}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}} {{.Path}}{{end}} +{{end}} ` const SingleVersionPackageOutput = ` -----{{.AnalyzeType}}----- Packages found in {{.Image}}:{{if not .Analysis}} None{{else}} -NAME VERSION SIZE{{range $name, $value := .Analysis}}{{"\n"}}{{print "-"}}{{$name}} {{$value.Version}} {{$value.Size}}{{end}}{{end}} +NAME VERSION SIZE{{range .Analysis}}{{"\n"}}{{print "-"}}{{.Name}} {{.Version}} {{.Size}}{{end}} +{{end}} ` diff --git a/vendor/github.com/docker/distribution/context/http.go b/vendor/github.com/docker/distribution/context/http.go index 7fe9b8ab..ae5ec2fc 100644 --- a/vendor/github.com/docker/distribution/context/http.go +++ b/vendor/github.com/docker/distribution/context/http.go @@ -8,7 +8,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + log "github.com/sirupsen/logrus" "github.com/docker/distribution/uuid" "github.com/gorilla/mux" ) diff --git a/vendor/github.com/docker/distribution/context/logger.go b/vendor/github.com/docker/distribution/context/logger.go index fbb6a051..86c5964e 100644 --- a/vendor/github.com/docker/distribution/context/logger.go +++ b/vendor/github.com/docker/distribution/context/logger.go @@ -3,7 +3,7 @@ package context import ( "fmt" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "runtime" ) diff --git a/vendor/github.com/docker/docker/api/common.go b/vendor/github.com/docker/docker/api/common.go index 142b7696..04061b2c 100644 --- a/vendor/github.com/docker/docker/api/common.go +++ b/vendor/github.com/docker/docker/api/common.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/system" diff --git a/vendor/github.com/docker/docker/pkg/system/syscall_windows.go b/vendor/github.com/docker/docker/pkg/system/syscall_windows.go index c328a6fb..f6c81292 100644 --- a/vendor/github.com/docker/docker/pkg/system/syscall_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/syscall_windows.go @@ -4,7 +4,7 @@ import ( "syscall" "unsafe" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" ) var (