diff --git a/.container-diff-tests.sh b/.container-diff-tests.sh index 4eed539f..353b69df 100755 --- a/.container-diff-tests.sh +++ b/.container-diff-tests.sh @@ -7,11 +7,19 @@ while IFS=$' \n\r' read -r flag differ image1 image2 file; do fi done < tests/differ_runs.txt +while IFS=$' \n\r' read -r flag analyzer image file; do + go run main.go $image $flag -j > $file + if [[ $? -ne 0 ]]; then + echo "container-diff" "$analyzer" "analyzer failed" + exit 1 + fi +done < tests/analyzer_runs.txt + success=0 -while IFS=$' \n\r' read -r differ actual expected; do +while IFS=$' \n\r' read -r type analyzer actual expected; do diff=$(jq --argfile a "$actual" --argfile b "$expected" -n 'def walk(f): . as $in | if type == "object" then reduce keys[] as $key ( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f elif type == "array" then map( walk(f) ) | f else f end; ($a | walk(if type == "array" then sort else . end)) as $a | ($b | walk(if type == "array" then sort else . end)) as $b | $a == $b') if ! "$diff" ; then - echo "container diff" "$differ" "diff output is not as expected" + echo "container-diff" "$analyzer" "$type" "output is not as expected" success=1 fi done < tests/diff_comparisons.txt diff --git a/README.md b/README.md index bd0175ff..48490885 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,13 @@ Status](https://travis-ci.org/GoogleCloudPlatform/container-diff.svg?branch=mast ## What is container-diff? -container-diff is an image differ command line tool. container-diff can diff two images along several different criteria, currently including: +container-diff is an image analysis command line tool. container-diff can analyze images along several different criteria, currently including: - Docker Image History - Image file system - apt-get installed packages - pip installed packages - npm installed packages +The above analyses can be performed on a single image, or a diff can be performed on two images to compare images. This tool can help you as a developer better understand what is changing within your images and better understand what your images contain. @@ -32,8 +33,18 @@ Download the [container-diff-windows-amd64.exe](https://storage.googleapis.com/c ## Quickstart -To use container-diff you need two Docker images (in the form of an ID, tarball, or URL from a repo). Once you have those images you can run any of the following differs: +To use container-diff to perform analysis on a single image, you need one Docker image (in the form of an ID, tarball, or URL from a repo). Once you have that image, you can run any of the following analyzers: +``` +container-diff [Run all analyzers] +container-diff -d [History] +container-diff -f [File System] +container-diff -p [Pip] +container-diff -a [Apt] +container-diff -n [Node] +``` + +To use container-diff to perform a diff analysis on two images, you need two Docker images (in the form of an ID, tarball, or URL from a repo). Once you have those images, you can run any of the following differs: ``` container-diff [Run all differs] container-diff -d [History] @@ -43,12 +54,13 @@ container-diff -a [Apt] container-diff -n [Node] ``` -You can similarly run many differs at once: +You can similarly run many differs or analyzers at once: ``` container-diff -d -a -n [History, Apt, and Node] ``` -All of the differ flags with their long versions can be seen below: + +All of the analyzer flags with their long versions can be seen below: | Differ | Short flag | Long Flag | | ------------------------- |:----------:| ----------:| @@ -71,8 +83,61 @@ To use the docker client instead of shelling out to your local docker daemon, ad ```container-diff -e``` +## Analysis Result Format + +The JSONs for analysis results are in the following format: +``` +{ + "Image": "foo", + "AnalyzeType": "Apt", + "Analysis": {}, +} +``` +The possible structures of the `Analysis` field are detailed below. + +### History Analysis + +The history analyzer outputs a list of strings representing descriptions of how an image layer was created. + +### Filesystem Analysis + +The filesystem analyzer outputs a list of strings representing filesystem 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: +``` +type PackageInfo struct { + Version string + Size string +} +``` + +#### Single Version Package Analysis + +Single version package analyzers (apt) have the following output structure: `map[string]PackageInfo` + +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` -## Output Format +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. + + +## Diff Result Format + +The JSONs for diff results are in the following format: +``` +{ + "Image1": "foo", + "Image2": "bar", + "DiffType": "Apt", + "Diff": {}, +} +``` +The possible structures of the `Diff` field are detailed below. ### History Diff @@ -80,21 +145,17 @@ The history differ has the following json output structure: ``` type HistDiff struct { - Image1 string - Image2 string Adds []string Dels []string } ``` -### File System Diff +### Filesystem Diff -The files system differ has the following json output structure: +The filesystem differ has the following json output structure: ``` type DirDiff struct { - Image1 string - Image2 string Adds []string Dels []string Mods []string @@ -105,44 +166,33 @@ type DirDiff struct { 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. -``` -type PackageInfo struct { - Version string - Size string -} -``` - -#### Single Version Diffs +#### Single Version Package Diffs Single version differs (apt) have the following json output structure: ``` type PackageDiff struct { - Image1 string Packages1 map[string]PackageInfo - Image2 string Packages2 map[string]PackageInfo InfoDiff []Info } ``` -Image1 and Image2 are the image names. 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 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. -#### Multi Version Diffs +#### Multi Version Package Diffs The multi version differs (pip, node) support processing images which may have multiple versions of the same package. Below is the json output structure: ``` type MultiVersionPackageDiff struct { - Image1 string Packages1 map[string]map[string]PackageInfo - Image2 string Packages2 map[string]map[string]PackageInfo InfoDiff []MultiVersionInfo } ``` -Image1 and Image2 are the image names. 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 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. ``` type MultiVersionInfo struct { @@ -190,46 +240,43 @@ Version differences: None ``` -## Make your own differ +## Make your own analyzer -Feel free to develop your own differ leveraging the utils currently available. PRs are welcome. +Feel free to develop your own analyzer leveraging the utils currently available. PRs are welcome. -### Custom Differ Quickstart +### Custom Analyzer Quickstart -In order to quickly make your own differ, follow these steps: +In order to quickly make your own analyzer, follow these steps: -1. Add your diff identifier to the flags in [root.go](https://github.com/GoogleCloudPlatform/container-diff/blob/ReadMe/cmd/root.go) -2. Determine if you can use existing differ tools. If you can make use of existing tools, you then need to construct the structs to feed into the diff tools by getting all of the packages for each image or the analogous quality to be diffed. To determine if you can leverage existing tools, think through these questions: -- Are you trying to diff packages? +1. Add your analyzer identifier to the flags in [root.go](https://github.com/GoogleCloudPlatform/container-diff/blob/ReadMe/cmd/root.go) +2. Determine if you can use existing analyzing or diffing tools. If you can make use of existing tools, you then need to construct the structs to feed into the tools by getting all of the packages for each image or the analogous quality to be analyzed. To determine if you can leverage existing tools, think through these questions: +- Are you trying to analyze packages? - Yes: Does the relevant package manager support different versions of the same package on one image? - - Yes: Use `GetMultiVerisonMapDiff` to diff `map[string]map[string]utils.PackageInfo` objects. See [nodeDiff.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/nodeDiff.go#L33) or [pipDiff.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/pipDiff.go#L23) for examples. - - No: Use `GetMapDiff` to diff `map[string]utils.PackageInfo` objects. See [aptDiff.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/aptDiff.go#L29). + - Yes: Implement `getPackages` to collect all versions of all packages within an image in a `map[string]map[string]PackageInfo`. Use `GetMultiVerisonMapDiff` to diff map objects. See [nodeDiff.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/nodeDiff.go#L33) or [pipDiff.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/pipDiff.go#L23) for examples. + - 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 Diff driver in the `differs` directory, such that you have a struct for your differ type and a method for that differ called Diff: +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: ``` -type YourDiffer struct {} +type YourAnalyzer struct {} -func (d YourDiffer) Diff(image1, image2 utils.Image) (DiffResult, error) {...} +func (a YourAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) {...} +func (a YourAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) {...} ``` -The arguments passed to your differ contain the path to the unpacked tar representation of the image. That path can be accessed as such: `image1.FSPath`. +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 diff (determined in step 2 - either `map[string]map[string]utils.PackageInfo` or `map[string]utils.PackageInfo`) and then call the appropriate get diff function (also determined in step2 - either `GetMultiVerisonMapDiff` or `GetMapDiff`). +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. -Otherwise, create your own differ which should yield information to fill a DiffResult in the next step. - -4. Create a DiffResult for your differ. +4. Create a result struct following either the AnalyzeResult or DiffResult interface by implementing the following two methods. ``` -type DiffResult interface { - GetStruct() DiffResult - OutputText(diffType string) error -} + GetStruct() DiffResult + OutputText(diffType string) error ``` -This is where you define how your differ should output for a human readable format (`OutputText`) and as a struct which can then be written to a `.json` file. See [output_utils.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/utils/output_utils.go). +This is where you define how your analyzer should output for a human readable format (`OutputText`) and as a struct which can then be written to a `.json` file. See [diff_output_utils.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/utils/diff_output_utils.go) and [analyze_output_utils.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/analyze_output_utils.go). -5. Add your differ to the diffs map in [differs.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/differs.go#L22) with the corresponding Differ struct as the value. +5. Add your analyzer to the `analyses` map in [differs.go](https://github.com/GoogleCloudPlatform/container-diff/blob/master/differs/differs.go#L22) with the corresponding Analyzer struct as the value. diff --git a/cmd/root.go b/cmd/root.go index 255e95de..66f9fb33 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,6 +6,7 @@ import ( goflag "flag" "fmt" "os" + "reflect" "sort" "sync" @@ -18,6 +19,7 @@ import ( var json bool var eng bool +var save bool var apt bool var node bool @@ -25,9 +27,7 @@ var file bool var history bool var pip bool -var save bool - -var diffFlagMap = map[string]*bool{ +var analyzeFlagMap = map[string]*bool{ "apt": &apt, "node": &node, "file": &file, @@ -36,9 +36,9 @@ var diffFlagMap = map[string]*bool{ } var RootCmd = &cobra.Command{ - Use: "[image1] [image2]", - Short: "Compare two images.", - Long: `Compares two images using the specifed differs as indicated via flags (see documentation for available differs).`, + Use: "To analyze a single image: [image]. To compare two images: [image1] [image2]", + Short: "Analyze a single image or compare two images.", + Long: `Analyzes a single image or compares two images using the specifed analyzers/differs as indicated via flags (see documentation for available ones).`, Run: func(cmd *cobra.Command, args []string) { if validArgs, err := validateArgs(args); !validArgs { glog.Error(err.Error()) @@ -47,104 +47,190 @@ var RootCmd = &cobra.Command{ utils.SetDockerEngine(eng) - img1Arg := args[0] - img2Arg := args[1] - diffArgs := []string{} - allDiffers := getAllDiffers() - for _, name := range allDiffers { - if *diffFlagMap[name] == true { - diffArgs = append(diffArgs, name) + analyzeArgs := []string{} + allAnalyzers := getAllAnalyzers() + for _, name := range allAnalyzers { + if *analyzeFlagMap[name] == true { + analyzeArgs = append(analyzeArgs, name) } } - // If no differs are specified, perform all diffs as the default - if len(diffArgs) == 0 { - diffArgs = allDiffers + + // If no differs/analyzers are specified, perform them all as the default + if len(analyzeArgs) == 0 { + analyzeArgs = allAnalyzers } - var wg sync.WaitGroup - wg.Add(2) + var err error + // In the case of one image, "analyzes" it + // In the case of two images, takes their "diff" + if len(args) == 1 { + err = analyzeImage(args[0], analyzeArgs) + } else { + err = diffImages(args[0], args[1], analyzeArgs) + } - glog.Infof("Starting diff on images %s and %s, using differs: %s", img1Arg, img2Arg, diffArgs) + if err != nil { + glog.Error(err) + os.Exit(1) + } + }, +} - var image1, image2 utils.Image - var err error - go func() { - defer wg.Done() - image1, err = utils.ImagePrepper{img1Arg}.GetImage() - if err != nil { - glog.Error(err.Error()) - os.Exit(1) - } - }() +func diffImages(image1Arg, image2Arg string, diffArgs []string) error { + var wg sync.WaitGroup + wg.Add(2) - go func() { - defer wg.Done() - image2, err = utils.ImagePrepper{img2Arg}.GetImage() - if err != nil { - glog.Error(err.Error()) - os.Exit(1) - } - }() + glog.Infof("Starting diff on images %s and %s, using differs: %s", image1Arg, image2Arg, diffArgs) - diffTypes, err := differs.GetDiffers(diffArgs) + var image1, image2 utils.Image + var err error + go func() { + defer wg.Done() + image1, err = utils.ImagePrepper{image1Arg}.GetImage() if err != nil { glog.Error(err.Error()) - os.Exit(1) } - wg.Wait() + }() - req := differs.DiffRequest{image1, image2, diffTypes} - if diffs, err := req.GetDiff(); err == nil { - // Outputs diff results in alphabetical order by differ name - diffTypes := []string{} - for name := range diffs { - diffTypes = append(diffTypes, name) - } - sort.Strings(diffTypes) - glog.Info("Retrieving diffs") - diffResults := []utils.DiffResult{} - for _, diffType := range diffTypes { - diff := diffs[diffType] - if json { - diffResults = append(diffResults, diff.GetStruct()) - } else { - err = diff.OutputText(diffType) - if err != nil { - glog.Error(err) - } + go func() { + defer wg.Done() + image2, err = utils.ImagePrepper{image2Arg}.GetImage() + if err != nil { + glog.Error(err.Error()) + } + }() + wg.Wait() + if err != nil { + cleanupImage(image1) + cleanupImage(image2) + return errors.New("Could not perform image diff") + } + + diffTypes, err := differs.GetAnalyzers(diffArgs) + if err != nil { + glog.Error(err.Error()) + cleanupImage(image1) + cleanupImage(image2) + return errors.New("Could not perform image diff") + } + + 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) + } + } + if !save { + cleanupImage(image1) + cleanupImage(image2) + + } else { + dir, _ := os.Getwd() + glog.Infof("Images were saved at %s as %s and %s", dir, image1.FSPath, image2.FSPath) + } + } else { + glog.Error(err.Error()) + cleanupImage(image1) + cleanupImage(image2) + return errors.New("Could not perform image diff") + } + + return nil +} + +func analyzeImage(imageArg string, analyzerArgs []string) error { + image, err := utils.ImagePrepper{imageArg}.GetImage() + if err != nil { + glog.Error(err.Error()) + cleanupImage(image) + return errors.New("Could not perform image analysis") + } + analyzeTypes, err := differs.GetAnalyzers(analyzerArgs) + if err != nil { + glog.Error(err.Error()) + cleanupImage(image) + return errors.New("Could not perform image analysis") + } + + 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 { - err = utils.JSONify(diffResults) + analyzeResults = append(analyzeResults, analysis.GetStruct()) + } else { + err = analysis.OutputText(analyzeType) if err != nil { glog.Error(err) } } - fmt.Println() - glog.Info("Removing image file system directories from system") - if !save { - errMsg := remove(image1.FSPath, true) - errMsg += remove(image2.FSPath, true) - if errMsg != "" { - glog.Error(errMsg) - } - } else { - dir, _ := os.Getwd() - glog.Infof("Images were saved at %s as %s and %s", dir, image1.FSPath, image2.FSPath) + } + if json { + err = utils.JSONify(analyzeResults) + if err != nil { + glog.Error(err) } + } + if !save { + cleanupImage(image) } else { - glog.Error(err.Error()) - os.Exit(1) + dir, _ := os.Getwd() + glog.Infof("Image was saved at %s as %s", dir, image.FSPath) } - }, + } else { + glog.Error(err.Error()) + cleanupImage(image) + return errors.New("Could not perform image analysis") + } + + return nil +} + +func cleanupImage(image utils.Image) { + if !reflect.DeepEqual(image, (utils.Image{})) { + glog.Infof("Removing image filesystem directory %s from system", image.FSPath) + errMsg := remove(image.FSPath, true) + if errMsg != "" { + glog.Error(errMsg) + } + } } -func getAllDiffers() []string { - allDiffers := []string{} - for name := range diffFlagMap { - allDiffers = append(allDiffers, name) +func getAllAnalyzers() []string { + allAnalyzers := []string{} + for name := range analyzeFlagMap { + allAnalyzers = append(allAnalyzers, name) } - return allDiffers + return allAnalyzers } func validateArgs(args []string) (bool, error) { @@ -165,11 +251,11 @@ func validateArgs(args []string) (bool, error) { func checkArgNum(args []string) (bool, error) { var errMessage string - if len(args) < 2 { - errMessage = "Too few arguments. Should have two images as arguments: [IMAGE1] [IMAGE2]." + if len(args) < 1 { + errMessage = "Too few arguments. Should have one or two images as arguments." return false, errors.New(errMessage) } else if len(args) > 2 { - errMessage = "Too many arguments. Should have two images as arguments: [IMAGE1] [IMAGE2]." + errMessage = "Too many arguments. Should have at most two images as arguments." return false, errors.New(errMessage) } else { return true, nil @@ -186,15 +272,12 @@ func checkImage(arg string) bool { func checkArgType(args []string) (bool, error) { var buffer bytes.Buffer valid := true - if !checkImage(args[0]) { - valid = false - errMessage := fmt.Sprintf("Argument %s is not an image ID, URL, or tar\n", args[0]) - buffer.WriteString(errMessage) - } - if !checkImage(args[1]) { - valid = false - errMessage := fmt.Sprintf("Argument %s is not an image ID, URL, or tar\n", args[1]) - buffer.WriteString(errMessage) + for _, arg := range args { + if !checkImage(arg) { + valid = false + errMessage := fmt.Sprintf("Argument %s is not an image ID, URL, or tar\n", args[0]) + buffer.WriteString(errMessage) + } } if !valid { return false, errors.New(buffer.String()) diff --git a/cmd/root_test.go b/cmd/root_test.go index 2328fd2e..709daded 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -11,7 +11,7 @@ type testpair struct { var argNumTests = []testpair{ {[]string{}, false}, - {[]string{"one"}, false}, + {[]string{"one"}, true}, {[]string{"one", "two"}, true}, {[]string{"one", "two", "three"}, false}, } diff --git a/differs/aptDiff.go b/differs/aptDiff.go index de5374d5..abc70070 100644 --- a/differs/aptDiff.go +++ b/differs/aptDiff.go @@ -10,16 +10,21 @@ import ( "github.com/golang/glog" ) -type AptDiffer struct { +type AptAnalyzer struct { } // AptDiff compares the packages installed by apt-get. -func (d AptDiffer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { - diff, err := singleVersionDiff(image1, image2, d) +func (a AptAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { + diff, err := singleVersionDiff(image1, image2, a) return diff, err } -func (d AptDiffer) getPackages(image utils.Image) (map[string]utils.PackageInfo, error) { +func (a AptAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { + analysis, err := singleVersionAnalysis(image, a) + return analysis, err +} + +func (a AptAnalyzer) getPackages(image utils.Image) (map[string]utils.PackageInfo, error) { path := image.FSPath packages := make(map[string]utils.PackageInfo) if _, err := os.Stat(path); err != nil { diff --git a/differs/aptDiff_test.go b/differs/aptDiff_test.go index 5643a638..dfb92b53 100644 --- a/differs/aptDiff_test.go +++ b/differs/aptDiff_test.go @@ -104,7 +104,7 @@ func TestGetAptPackages(t *testing.T) { }, } for _, test := range testCases { - d := AptDiffer{} + d := AptAnalyzer{} image := utils.Image{FSPath: test.path} packages, err := d.getPackages(image) if err != nil && !test.err { diff --git a/differs/differs.go b/differs/differs.go index 5bbc7af5..d723e396 100644 --- a/differs/differs.go +++ b/differs/differs.go @@ -12,25 +12,31 @@ import ( type DiffRequest struct { Image1 utils.Image Image2 utils.Image - DiffTypes []Differ + DiffTypes []Analyzer } -type Differ interface { +type SingleRequest struct { + Image utils.Image + AnalyzeTypes []Analyzer +} + +type Analyzer interface { Diff(image1, image2 utils.Image) (utils.DiffResult, error) + Analyze(image utils.Image) (utils.AnalyzeResult, error) } -var diffs = map[string]Differ{ - "history": HistoryDiffer{}, - "file": FileDiffer{}, - "apt": AptDiffer{}, - "pip": PipDiffer{}, - "node": NodeDiffer{}, +var analyzers = map[string]Analyzer{ + "history": HistoryAnalyzer{}, + "file": FileAnalyzer{}, + "apt": AptAnalyzer{}, + "pip": PipAnalyzer{}, + "node": NodeAnalyzer{}, } -func (diff DiffRequest) GetDiff() (map[string]utils.DiffResult, error) { - img1 := diff.Image1 - img2 := diff.Image2 - diffs := diff.DiffTypes +func (req DiffRequest) GetDiff() (map[string]utils.DiffResult, error) { + img1 := req.Image1 + img2 := req.Image2 + diffs := req.DiffTypes results := map[string]utils.DiffResult{} for _, differ := range diffs { @@ -52,16 +58,40 @@ func (diff DiffRequest) GetDiff() (map[string]utils.DiffResult, error) { return results, err } -func GetDiffers(diffNames []string) (diffFuncs []Differ, err error) { - for _, diffName := range diffNames { - if d, exists := diffs[diffName]; exists { - diffFuncs = append(diffFuncs, d) +func (req SingleRequest) GetAnalysis() (map[string]utils.AnalyzeResult, error) { + img := req.Image + analyses := req.AnalyzeTypes + + results := map[string]utils.AnalyzeResult{} + for _, analyzer := range analyses { + analyzeName := reflect.TypeOf(analyzer).Name() + if analysis, err := analyzer.Analyze(img); err == nil { + results[analyzeName] = analysis + } else { + glog.Errorf("Error getting analysis with %s: %s", analyzeName, err) + } + } + + var err error + if len(results) == 0 { + err = fmt.Errorf("Could not perform analysis on %s", img) + } else { + err = nil + } + + return results, err +} + +func GetAnalyzers(analyzeNames []string) (analyzeFuncs []Analyzer, err error) { + for _, name := range analyzeNames { + if a, exists := analyzers[name]; exists { + analyzeFuncs = append(analyzeFuncs, a) } else { - glog.Errorf("Unknown differ specified", diffName) + glog.Errorf("Unknown analyzer/differ specified", name) } } - if len(diffFuncs) == 0 { - err = errors.New("No known differs specified") + if len(analyzeFuncs) == 0 { + err = errors.New("No known analyzers/differs specified") } return } diff --git a/differs/fileDiff.go b/differs/fileDiff.go index 7f883f04..ae6c07f0 100644 --- a/differs/fileDiff.go +++ b/differs/fileDiff.go @@ -6,13 +6,32 @@ import ( "github.com/GoogleCloudPlatform/container-diff/utils" ) -type FileDiffer struct { +type FileAnalyzer struct { } // FileDiff diffs two packages and compares their contents -func (d FileDiffer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a FileAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { diff, err := diffImageFiles(image1, image2) - return &utils.DirDiffResult{DiffType: "FileDiffer", Diff: diff}, err + return &utils.DirDiffResult{ + Image1: image1.Source, + Image2: image2.Source, + DiffType: "File", + Diff: diff, + }, err +} + +func (a FileAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { + var result utils.ListAnalyzeResult + + imgDir, err := utils.GetDirectory(image.FSPath, true) + if err != nil { + return result, err + } + + result.Image = image.Source + result.AnalyzeType = "File" + result.Analysis = imgDir.Content + return &result, err } func diffImageFiles(image1, image2 utils.Image) (utils.DirDiff, error) { @@ -36,11 +55,9 @@ func diffImageFiles(image1, image2 utils.Image) (utils.DirDiff, error) { sort.Strings(dels) diff = utils.DirDiff{ - Image1: image1.Source, - Image2: image2.Source, - Adds: adds, - Dels: dels, - Mods: []string{}, + Adds: adds, + Dels: dels, + Mods: []string{}, } return diff, nil } diff --git a/differs/historyDiff.go b/differs/historyDiff.go index 0bd81fe8..82c664e0 100644 --- a/differs/historyDiff.go +++ b/differs/historyDiff.go @@ -6,12 +6,27 @@ import ( "github.com/GoogleCloudPlatform/container-diff/utils" ) -type HistoryDiffer struct { +type HistoryAnalyzer struct { } -func (d HistoryDiffer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { +func (a HistoryAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { diff, err := getHistoryDiff(image1, image2) - return &utils.HistDiffResult{DiffType: "HistoryDiffer", Diff: diff}, err + return &utils.HistDiffResult{ + Image1: image1.Source, + Image2: image2.Source, + DiffType: "History", + Diff: diff, + }, err +} + +func (a HistoryAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { + history := getHistoryList(image.Config.History) + result := utils.ListAnalyzeResult{ + Image: image.Source, + AnalyzeType: "History", + Analysis: history, + } + return &result, nil } func getHistoryDiff(image1, image2 utils.Image) (utils.HistDiff, error) { @@ -20,7 +35,7 @@ func getHistoryDiff(image1, image2 utils.Image) (utils.HistDiff, error) { adds := utils.GetAdditions(history1, history2) dels := utils.GetDeletions(history1, history2) - diff := utils.HistDiff{image1.Source, image2.Source, adds, dels} + diff := utils.HistDiff{adds, dels} return diff, nil } diff --git a/differs/nodeDiff.go b/differs/nodeDiff.go index 8614ab4a..1086a97b 100644 --- a/differs/nodeDiff.go +++ b/differs/nodeDiff.go @@ -12,22 +12,21 @@ import ( "github.com/golang/glog" ) -type NodeDiffer struct { +type NodeAnalyzer struct { } // NodeDiff compares the packages installed by apt-get. -func (d NodeDiffer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { - diff, err := multiVersionDiff(image1, image2, d) +func (a NodeAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { + diff, err := multiVersionDiff(image1, image2, a) return diff, err } -func buildNodePaths(path string) ([]string, error) { - globalPaths := filepath.Join(path, "node_modules") - localPath := filepath.Join(path, "usr/local/lib/node_modules") - return []string{globalPaths, localPath}, nil +func (a NodeAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { + analysis, err := multiVersionAnalysis(image, a) + return analysis, err } -func (d NodeDiffer) getPackages(image utils.Image) (map[string]map[string]utils.PackageInfo, error) { +func (a NodeAnalyzer) getPackages(image utils.Image) (map[string]map[string]utils.PackageInfo, error) { path := image.FSPath packages := make(map[string]map[string]utils.PackageInfo) if _, err := os.Stat(path); err != nil { @@ -83,6 +82,12 @@ type nodePackage struct { Version string `json:"version"` } +func buildNodePaths(path string) ([]string, error) { + globalPaths := filepath.Join(path, "node_modules") + localPath := filepath.Join(path, "usr/local/lib/node_modules") + return []string{globalPaths, localPath}, nil +} + func readPackageJSON(path string) (nodePackage, error) { var currPackage nodePackage jsonBytes, err := ioutil.ReadFile(path) diff --git a/differs/nodeDiff_test.go b/differs/nodeDiff_test.go index 840342a1..85b4e839 100644 --- a/differs/nodeDiff_test.go +++ b/differs/nodeDiff_test.go @@ -45,7 +45,7 @@ func TestGetNodePackages(t *testing.T) { for _, test := range testCases { image := utils.Image{FSPath: test.path} - d := NodeDiffer{} + d := NodeAnalyzer{} packages, err := d.getPackages(image) if err != nil && !test.err { t.Errorf("Got unexpected error: %s", err) diff --git a/differs/package_differs.go b/differs/package_differs.go index 01828c8e..475b13cf 100644 --- a/differs/package_differs.go +++ b/differs/package_differs.go @@ -2,19 +2,20 @@ package differs import ( "reflect" + "strings" "github.com/GoogleCloudPlatform/container-diff/utils" ) -type MultiVersionPackageDiffer interface { +type MultiVersionPackageAnalyzer interface { getPackages(image utils.Image) (map[string]map[string]utils.PackageInfo, error) } -type SingleVersionPackageDiffer interface { +type SingleVersionPackageAnalyzer interface { getPackages(image utils.Image) (map[string]utils.PackageInfo, error) } -func multiVersionDiff(image1, image2 utils.Image, differ MultiVersionPackageDiffer) (utils.DiffResult, error) { +func multiVersionDiff(image1, image2 utils.Image, differ MultiVersionPackageAnalyzer) (utils.DiffResult, error) { pack1, err := differ.getPackages(image1) if err != nil { return &utils.MultiVersionPackageDiffResult{}, err @@ -24,22 +25,58 @@ func multiVersionDiff(image1, image2 utils.Image, differ MultiVersionPackageDiff return &utils.MultiVersionPackageDiffResult{}, err } - diff := utils.GetMultiVersionMapDiff(pack1, pack2, image1.Source, image2.Source) - diff.DiffType = reflect.TypeOf(differ).Name() - return &diff, nil + diff := utils.GetMultiVersionMapDiff(pack1, pack2) + return &utils.MultiVersionPackageDiffResult{ + Image1: image1.Source, + Image2: image2.Source, + DiffType: strings.TrimSuffix(reflect.TypeOf(differ).Name(), "Analyzer"), + Diff: diff, + }, nil } -func singleVersionDiff(image1, image2 utils.Image, differ SingleVersionPackageDiffer) (utils.DiffResult, error) { +func singleVersionDiff(image1, image2 utils.Image, differ SingleVersionPackageAnalyzer) (utils.DiffResult, error) { pack1, err := differ.getPackages(image1) if err != nil { - return &utils.PackageDiffResult{}, err + return &utils.SingleVersionPackageDiffResult{}, err } pack2, err := differ.getPackages(image2) if err != nil { - return &utils.PackageDiffResult{}, err + return &utils.SingleVersionPackageDiffResult{}, err } - diff := utils.GetMapDiff(pack1, pack2, image1.Source, image2.Source) - diff.DiffType = reflect.TypeOf(differ).Name() - return &diff, nil + diff := utils.GetMapDiff(pack1, pack2) + return &utils.SingleVersionPackageDiffResult{ + Image1: image1.Source, + Image2: image2.Source, + DiffType: strings.TrimSuffix(reflect.TypeOf(differ).Name(), "Analyzer"), + Diff: diff, + }, nil +} + +func multiVersionAnalysis(image utils.Image, analyzer MultiVersionPackageAnalyzer) (utils.AnalyzeResult, error) { + pack, err := analyzer.getPackages(image) + if err != nil { + return &utils.MultiVersionPackageAnalyzeResult{}, err + } + + analysis := utils.MultiVersionPackageAnalyzeResult{ + Image: image.Source, + AnalyzeType: strings.TrimSuffix(reflect.TypeOf(analyzer).Name(), "Analyzer"), + Analysis: pack, + } + return &analysis, nil +} + +func singleVersionAnalysis(image utils.Image, analyzer SingleVersionPackageAnalyzer) (utils.AnalyzeResult, error) { + pack, err := analyzer.getPackages(image) + if err != nil { + return &utils.SingleVersionPackageAnalyzeResult{}, err + } + + analysis := utils.SingleVersionPackageAnalyzeResult{ + Image: image.Source, + AnalyzeType: strings.TrimSuffix(reflect.TypeOf(analyzer).Name(), "Analyzer"), + Analysis: pack, + } + return &analysis, nil } diff --git a/differs/pipDiff.go b/differs/pipDiff.go index aaa9de8f..17849d4a 100644 --- a/differs/pipDiff.go +++ b/differs/pipDiff.go @@ -12,54 +12,29 @@ import ( "github.com/golang/glog" ) -type PipDiffer struct { +type PipAnalyzer struct { } // PipDiff compares pip-installed Python packages between layers of two different images. -func (d PipDiffer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { - diff, err := multiVersionDiff(image1, image2, d) +func (a PipAnalyzer) Diff(image1, image2 utils.Image) (utils.DiffResult, error) { + diff, err := multiVersionDiff(image1, image2, a) return diff, err } -func getPythonVersion(pathToImage string) ([]string, error) { - matches := []string{} - libPath := filepath.Join(pathToImage, "usr/local/lib") - libContents, err := ioutil.ReadDir(libPath) - if err != nil { - return matches, err - } - - for _, file := range libContents { - pattern := regexp.MustCompile("^python[0-9]+\\.[0-9]+$") - match := pattern.FindString(file.Name()) - if match != "" { - matches = append(matches, match) - } - } - return matches, nil -} - -func getPythonPaths(vars []string) []string { - paths := []string{} - for _, envVar := range vars { - pythonPathPattern := regexp.MustCompile("^PYTHONPATH=(.*)") - match := pythonPathPattern.FindStringSubmatch(envVar) - if len(match) != 0 { - pythonPath := match[1] - paths = strings.Split(pythonPath, ":") - break - } - } - return paths +func (a PipAnalyzer) Analyze(image utils.Image) (utils.AnalyzeResult, error) { + analysis, err := multiVersionAnalysis(image, a) + return analysis, err } -func (d PipDiffer) getPackages(image utils.Image) (map[string]map[string]utils.PackageInfo, error) { +func (a PipAnalyzer) getPackages(image utils.Image) (map[string]map[string]utils.PackageInfo, error) { path := image.FSPath packages := make(map[string]map[string]utils.PackageInfo) pythonPaths := []string{} if !reflect.DeepEqual(utils.ConfigSchema{}, image.Config) { paths := getPythonPaths(image.Config.Config.Env) - pythonPaths = append(pythonPaths, paths...) + for _, p := range paths { + pythonPaths = append(pythonPaths, p) + } } pythonVersions, err := getPythonVersion(path) if err != nil { @@ -75,7 +50,7 @@ func (d PipDiffer) getPackages(image utils.Image) (map[string]map[string]utils.P for _, pythonPath := range pythonPaths { contents, err := ioutil.ReadDir(pythonPath) if err != nil { - // python version folder doesn't have a site-packages folder or PYTHONPATH doesn't exist + // python version folder doesn't have a site-packages folder continue } @@ -128,3 +103,35 @@ func addToMap(packages map[string]map[string]utils.PackageInfo, pack string, pat } packages[pack][path] = packInfo } + +func getPythonVersion(pathToLayer string) ([]string, error) { + matches := []string{} + libPath := filepath.Join(pathToLayer, "usr/local/lib") + libContents, err := ioutil.ReadDir(libPath) + if err != nil { + return matches, err + } + + for _, file := range libContents { + pattern := regexp.MustCompile("^python[0-9]+\\.[0-9]+$") + match := pattern.FindString(file.Name()) + if match != "" { + matches = append(matches, match) + } + } + return matches, nil +} + +func getPythonPaths(vars []string) []string { + paths := []string{} + for _, envVar := range vars { + pythonPathPattern := regexp.MustCompile("^PYTHONPATH=(.*)") + match := pythonPathPattern.FindStringSubmatch(envVar) + if len(match) != 0 { + pythonPath := match[1] + paths = strings.Split(pythonPath, ":") + break + } + } + return paths +} diff --git a/differs/pipDiff_test.go b/differs/pipDiff_test.go index afdfccae..d99c0d3b 100644 --- a/differs/pipDiff_test.go +++ b/differs/pipDiff_test.go @@ -134,7 +134,7 @@ func TestGetPythonPackages(t *testing.T) { }, } for _, test := range testCases { - d := PipDiffer{} + d := PipAnalyzer{} packages, _ := d.getPackages(test.image) if !reflect.DeepEqual(packages, test.expectedPackages) { t.Errorf("%s\nExpected: %s\nGot: %s", test.descrip, test.expectedPackages, packages) diff --git a/tests/analyzer_runs.txt b/tests/analyzer_runs.txt new file mode 100644 index 00000000..7f8ffb75 --- /dev/null +++ b/tests/analyzer_runs.txt @@ -0,0 +1,3 @@ +-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 diff --git a/tests/apt_analysis_expected.json b/tests/apt_analysis_expected.json new file mode 100644 index 00000000..7b6fcecd --- /dev/null +++ b/tests/apt_analysis_expected.json @@ -0,0 +1,408 @@ +[ + { + "Image": "gcr.io/gcp-runtimes/apt-modified", + "AnalyzeType": "Apt", + "Analysis": { + "adduser": { + "Version": "3.115", + "Size": "849" + }, + "apt": { + "Version": "1.4.6", + "Size": "3538" + }, + "base-files": { + "Version": "9.9", + "Size": "333" + }, + "base-passwd": { + "Version": "3.5.43", + "Size": "229" + }, + "bash": { + "Version": "4.4-5", + "Size": "5798" + }, + "bsdutils": { + "Version": "1:2.29.2-1", + "Size": "238" + }, + "bzip2": { + "Version": "1.0.6-8.1", + "Size": "184" + }, + "coreutils": { + "Version": "8.26-3", + "Size": "15103" + }, + "dash": { + "Version": "0.5.8-2.4", + "Size": "204" + }, + "debconf": { + "Version": "1.5.61", + "Size": "558" + }, + "debian-archive-keyring": { + "Version": "2017.5", + "Size": "118" + }, + "debianutils": { + "Version": "4.8.1.1", + "Size": "213" + }, + "diffutils": { + "Version": "1:3.5-3", + "Size": "1327" + }, + "dpkg": { + "Version": "1.18.24", + "Size": "6745" + }, + "e2fslibs": { + "Version": "1.43.4-2", + "Size": "449" + }, + "e2fsprogs": { + "Version": "1.43.4-2", + "Size": "4022" + }, + "file": { + "Version": "1:5.30-1", + "Size": "96" + }, + "findutils": { + "Version": "4.6.0 git+20161106-2", + "Size": "1854" + }, + "gcc-6-base": { + "Version": "6.3.0-18", + "Size": "209" + }, + "gpgv": { + "Version": "2.1.18-6", + "Size": "721" + }, + "grep": { + "Version": "2.27-2", + "Size": "1131" + }, + "gzip": { + "Version": "1.6-5 b1", + "Size": "231" + }, + "hostname": { + "Version": "3.18 b1", + "Size": "47" + }, + "inetutils-ping": { + "Version": "2:1.9.4-2 b1", + "Size": "337" + }, + "init-system-helpers": { + "Version": "1.48", + "Size": "131" + }, + "iproute2": { + "Version": "4.9.0-1", + "Size": "1753" + }, + "libacl1": { + "Version": "2.2.52-3 b1", + "Size": "62" + }, + "libapt-pkg5.0": { + "Version": "1.4.6", + "Size": "3055" + }, + "libattr1": { + "Version": "1:2.4.47-2 b2", + "Size": "42" + }, + "libaudit-common": { + "Version": "1:2.6.7-2", + "Size": "30" + }, + "libaudit1": { + "Version": "1:2.6.7-2", + "Size": "150" + }, + "libblkid1": { + "Version": "2.29.2-1", + "Size": "367" + }, + "libbz2-1.0": { + "Version": "1.0.6-8.1", + "Size": "96" + }, + "libc-bin": { + "Version": "2.24-11 deb9u1", + "Size": "3365" + }, + "libc6": { + "Version": "2.24-11 deb9u1", + "Size": "10684" + }, + "libcap-ng0": { + "Version": "0.7.7-3 b1", + "Size": "43" + }, + "libcomerr2": { + "Version": "1.43.4-2", + "Size": "83" + }, + "libdb5.3": { + "Version": "5.3.28-12 b1", + "Size": "1815" + }, + "libdebconfclient0": { + "Version": "0.227", + "Size": "67" + }, + "libelf1": { + "Version": "0.168-1", + "Size": "934" + }, + "libexpat1": { + "Version": "2.2.0-2 deb9u1", + "Size": "369" + }, + "libfdisk1": { + "Version": "2.29.2-1", + "Size": "469" + }, + "libffi6": { + "Version": "3.2.1-6", + "Size": "56" + }, + "libgcc1": { + "Version": "1:6.3.0-18", + "Size": "108" + }, + "libgcrypt20": { + "Version": "1.7.6-2", + "Size": "1262" + }, + "libgpg-error0": { + "Version": "1.26-2", + "Size": "572" + }, + "liblz4-1": { + "Version": "0.0~r131-2 b1", + "Size": "93" + }, + "liblzma5": { + "Version": "5.2.2-1.2 b1", + "Size": "339" + }, + "libmagic-mgc": { + "Version": "1:5.30-1", + "Size": "4832" + }, + "libmagic1": { + "Version": "1:5.30-1", + "Size": "214" + }, + "libmnl0": { + "Version": "1.0.4-2", + "Size": "46" + }, + "libmount1": { + "Version": "2.29.2-1", + "Size": "403" + }, + "libncursesw5": { + "Version": "6.0 20161126-1", + "Size": "347" + }, + "libpam-modules": { + "Version": "1.1.8-3.6", + "Size": "874" + }, + "libpam-modules-bin": { + "Version": "1.1.8-3.6", + "Size": "220" + }, + "libpam-runtime": { + "Version": "1.1.8-3.6", + "Size": "1016" + }, + "libpam0g": { + "Version": "1.1.8-3.6", + "Size": "229" + }, + "libpcre3": { + "Version": "2:8.39-3", + "Size": "668" + }, + "libpython-stdlib": { + "Version": "2.7.13-2", + "Size": "37" + }, + "libpython2.7-minimal": { + "Version": "2.7.13-2", + "Size": "2767" + }, + "libpython2.7-stdlib": { + "Version": "2.7.13-2", + "Size": "8550" + }, + "libreadline7": { + "Version": "7.0-3", + "Size": "416" + }, + "libselinux1": { + "Version": "2.6-3 b1", + "Size": "209" + }, + "libsemanage-common": { + "Version": "2.6-2", + "Size": "39" + }, + "libsemanage1": { + "Version": "2.6-2", + "Size": "291" + }, + "libsepol1": { + "Version": "2.6-2", + "Size": "653" + }, + "libsmartcols1": { + "Version": "2.29.2-1", + "Size": "257" + }, + "libsqlite3-0": { + "Version": "3.16.2-5", + "Size": "1162" + }, + "libss2": { + "Version": "1.43.4-2", + "Size": "95" + }, + "libssl1.1": { + "Version": "1.1.0f-3", + "Size": "3524" + }, + "libstdc++6": { + "Version": "6.3.0-18", + "Size": "1998" + }, + "libsystemd0": { + "Version": "232-25", + "Size": "652" + }, + "libtinfo5": { + "Version": "6.0 20161126-1", + "Size": "478" + }, + "libudev1": { + "Version": "232-25", + "Size": "222" + }, + "libustr-1.0-1": { + "Version": "1.0.4-6", + "Size": "258" + }, + "libuuid1": { + "Version": "2.29.2-1", + "Size": "107" + }, + "login": { + "Version": "1:4.4-4.1", + "Size": "2747" + }, + "lsb-base": { + "Version": "9.20161125", + "Size": "49" + }, + "mawk": { + "Version": "1.3.3-17 b3", + "Size": "183" + }, + "mime-support": { + "Version": "3.60", + "Size": "110" + }, + "mount": { + "Version": "2.29.2-1", + "Size": "444" + }, + "multiarch-support": { + "Version": "2.24-11 deb9u1", + "Size": "220" + }, + "ncurses-base": { + "Version": "6.0 20161126-1", + "Size": "340" + }, + "ncurses-bin": { + "Version": "6.0 20161126-1", + "Size": "532" + }, + "netbase": { + "Version": "5.4", + "Size": "44" + }, + "passwd": { + "Version": "1:4.4-4.1", + "Size": "2478" + }, + "perl-base": { + "Version": "5.24.1-3", + "Size": "7539" + }, + "python": { + "Version": "2.7.13-2", + "Size": "648" + }, + "python-minimal": { + "Version": "2.7.13-2", + "Size": "145" + }, + "python2.7": { + "Version": "2.7.13-2", + "Size": "359" + }, + "python2.7-minimal": { + "Version": "2.7.13-2", + "Size": "3820" + }, + "readline-common": { + "Version": "7.0-3", + "Size": "89" + }, + "sed": { + "Version": "4.4-1", + "Size": "799" + }, + "sensible-utils": { + "Version": "0.0.9", + "Size": "110" + }, + "sysvinit-utils": { + "Version": "2.88dsf-59.9", + "Size": "110" + }, + "tar": { + "Version": "1.29b-1.1", + "Size": "2770" + }, + "tzdata": { + "Version": "2017b-1", + "Size": "3010" + }, + "util-linux": { + "Version": "2.29.2-1", + "Size": "3558" + }, + "xz-utils": { + "Version": "5.2.2-1.2 b1", + "Size": "516" + }, + "zlib1g": { + "Version": "1:1.2.8.dfsg-5", + "Size": "156" + } + } + } +] \ No newline at end of file diff --git a/tests/apt_diff_expected.json b/tests/apt_diff_expected.json index 26bd1c43..decb8f29 100644 --- a/tests/apt_diff_expected.json +++ b/tests/apt_diff_expected.json @@ -1,8 +1,9 @@ [ { - "DiffType": "AptDiffer", + "Image1": "gcr.io/gcp-runtimes/apt-base", + "Image2": "gcr.io/gcp-runtimes/apt-modified", + "DiffType": "Apt", "Diff": { - "Image1": "gcr.io/gcp-runtimes/apt-base", "Packages1": { "dh-python": { "Version": "2.20170125", @@ -41,7 +42,6 @@ "Size": "9411" } }, - "Image2": "gcr.io/gcp-runtimes/apt-modified", "Packages2": { "libffi6": { "Version": "3.2.1-6", diff --git a/tests/diff_comparisons.txt b/tests/diff_comparisons.txt index a2b352cd..c06884f4 100644 --- a/tests/diff_comparisons.txt +++ b/tests/diff_comparisons.txt @@ -1,8 +1,8 @@ -file tests/file_diff_actual.json tests/file_diff_expected.json -pip tests/pip_diff_actual.json tests/pip_diff_expected.json -apt tests/apt_diff_actual.json tests/apt_diff_expected.json -node tests/node_diff_actual.json tests/node_diff_expected.json -node tests/node_diff_order_actual.json tests/node_diff_order_expected.json -multi tests/multi_diff_actual.json tests/multi_diff_expected.json -history tests/hist_diff_actual.json tests/hist_diff_expected.json -multihist tests/multi_hist_diff_actual.json tests/multi_hist_diff_expected.json +file diff tests/file_diff_actual.json tests/file_diff_expected.json +apt diff tests/apt_diff_actual.json tests/apt_diff_expected.json +nodeOrder diff tests/node_diff_order_actual.json tests/node_diff_order_expected.json +multi diff tests/multi_diff_actual.json tests/multi_diff_expected.json +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 diff --git a/tests/differ_runs.txt b/tests/differ_runs.txt index 58ba16d6..86840f9c 100644 --- a/tests/differ_runs.txt +++ b/tests/differ_runs.txt @@ -1,8 +1,5 @@ -f file gcr.io/gcp-runtimes/diff-base gcr.io/gcp-runtimes/diff-modified tests/file_diff_actual.json --p pip gcr.io/gcp-runtimes/pip-base gcr.io/gcp-runtimes/pip-modified tests/pip_diff_actual.json -a apt gcr.io/gcp-runtimes/apt-base gcr.io/gcp-runtimes/apt-modified tests/apt_diff_actual.json --n node gcr.io/gcp-runtimes/node-base gcr.io/gcp-runtimes/node-modified tests/node_diff_actual.json -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 --pd multihist gcr.io/gcp-runtimes/pip-base gcr.io/gcp-runtimes/pip-modified tests/multi_hist_diff_actual.json diff --git a/tests/file_diff_expected.json b/tests/file_diff_expected.json index ae85a0ec..b0aad484 100644 --- a/tests/file_diff_expected.json +++ b/tests/file_diff_expected.json @@ -1,9 +1,9 @@ [ { - "DiffType": "FileDiffer", + "Image1": "gcr.io/gcp-runtimes/diff-base", + "Image2": "gcr.io/gcp-runtimes/diff-modified", + "DiffType": "File", "Diff": { - "Image1": "gcr.io/gcp-runtimes/diff-base", - "Image2": "gcr.io/gcp-runtimes/diff-modified", "Adds": [], "Dels": [ "/home/test" diff --git a/tests/hist_diff_expected.json b/tests/hist_diff_expected.json index ec406aff..e46ee7fc 100644 --- a/tests/hist_diff_expected.json +++ b/tests/hist_diff_expected.json @@ -1,9 +1,9 @@ [ { - "DiffType": "HistoryDiffer", + "Image1": "gcr.io/gcp-runtimes/diff-base", + "Image2": "gcr.io/gcp-runtimes/diff-modified", + "DiffType": "History", "Diff": { - "Image1": "gcr.io/gcp-runtimes/diff-base", - "Image2": "gcr.io/gcp-runtimes/diff-modified", "Adds": [ "/bin/sh -c #(nop) ADD file:aa56bc8f2fea9c0c81ca085bfa273ad1a3b0d46f51b8c9c61b483340c902024f in /" ], diff --git a/tests/multi_diff_expected.json b/tests/multi_diff_expected.json index a6a4359a..a315a234 100644 --- a/tests/multi_diff_expected.json +++ b/tests/multi_diff_expected.json @@ -1,152 +1,152 @@ [ - { - "DiffType": "AptDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/multi-base", - "Packages1": {}, - "Image2": "gcr.io/gcp-runtimes/multi-modified", - "Packages2": { - "dh-python": { - "Version": "1.20141111-2", - "Size": "277" - }, - "libmpdec2": { - "Version": "2.4.1-1", - "Size": "275" - }, - "libpython3-stdlib": { - "Version": "3.4.2-2", - "Size": "28" - }, - "libpython3.4-minimal": { - "Version": "3.4.2-1", - "Size": "3310" - }, - "libpython3.4-stdlib": { - "Version": "3.4.2-1", - "Size": "9484" - }, - "python3": { - "Version": "3.4.2-2", - "Size": "36" - }, - "python3-minimal": { - "Version": "3.4.2-2", - "Size": "96" - }, - "python3.4": { - "Version": "3.4.2-1", - "Size": "336" - }, - "python3.4-minimal": { - "Version": "3.4.2-1", - "Size": "4506" + { + "Image2": "gcr.io/gcp-runtimes/multi-modified", + "Image1": "gcr.io/gcp-runtimes/multi-base", + "DiffType": "Apt", + "Diff": { + "Packages1": {}, + "Packages2": { + "libmpdec2": { + "Version": "2.4.1-1", + "Size": "275" + }, + "python3.4-minimal": { + "Version": "3.4.2-1", + "Size": "4506" + }, + "libpython3-stdlib": { + "Version": "3.4.2-2", + "Size": "28" + }, + "python3-minimal": { + "Version": "3.4.2-2", + "Size": "96" + }, + "python3.4": { + "Version": "3.4.2-1", + "Size": "336" + }, + "dh-python": { + "Version": "1.20141111-2", + "Size": "277" + }, + "libpython3.4-minimal": { + "Version": "3.4.2-1", + "Size": "3310" + }, + "libpython3.4-stdlib": { + "Version": "3.4.2-1", + "Size": "9484" + }, + "python3": { + "Version": "3.4.2-2", + "Size": "36" + } + }, + "InfoDiff": [] } - }, - "InfoDiff": [] - } - }, - { - "DiffType": "NodeDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/multi-base", - "Packages1": {}, - "Image2": "gcr.io/gcp-runtimes/multi-modified", - "Packages2": { - "pax": { - "/node_modules/pax/": { - "Version": "0.2.1", - "Size": "11998" - } - } - }, - "InfoDiff": [ - { - "Package": "sax", - "Info1": [ - { - "Version": "1.2.4", - "Size": "56382" - } - ], - "Info2": [ - { - "Version": "0.1.1", - "Size": "127107" - } - ] - } - ] - } - }, - { - "DiffType": "PipDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/multi-base", - "Packages1": { - "pbr": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "3.1.1", - "Size": "447110" - } - } - }, - "Image2": "gcr.io/gcp-runtimes/multi-modified", - "Packages2": { - "retrying": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "1.3.3", - "Size": "9955" - } + }, + { + "Image2": "gcr.io/gcp-runtimes/multi-modified", + "Image1": "gcr.io/gcp-runtimes/multi-base", + "DiffType": "Node", + "Diff": { + "Packages1": {}, + "Packages2": { + "pax": { + "/node_modules/pax/": { + "Version": "0.2.1", + "Size": "11998" + } + } + }, + "InfoDiff": [ + { + "Info1": [ + { + "Version": "1.2.4", + "Size": "56382" + } + ], + "Info2": [ + { + "Version": "0.1.1", + "Size": "127107" + } + ], + "Package": "sax" + } + ] } - }, - "InfoDiff": [ - { - "Package": "mock", - "Info1": [ - { - "Version": "2.0.0", - "Size": "504226" - } - ], - "Info2": [ - { - "Version": "0.8.0", - "Size": "73348" - } - ] - }, - { - "Package": "setuptools", - "Info1": [ - { - "Version": "36.2.2", - "Size": "839895" - } - ], - "Info2": [ - { - "Version": "36.2.2", - "Size": "1157078" - } - ] - }, - { - "Package": "wheel", - "Info1": [ - { - "Version": "0.29.0", - "Size": "103509" - } - ], - "Info2": [ - { - "Version": "0.29.0", - "Size": "137451" - } - ] + }, + { + "Image2": "gcr.io/gcp-runtimes/multi-modified", + "Image1": "gcr.io/gcp-runtimes/multi-base", + "DiffType": "Pip", + "Diff": { + "Packages1": { + "pbr": { + "/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" + } + } + }, + "InfoDiff": [ + { + "Info1": [ + { + "Version": "36.2.2", + "Size": "839895" + } + ], + "Info2": [ + { + "Version": "36.2.2", + "Size": "1157078" + } + ], + "Package": "setuptools" + }, + { + "Info1": [ + { + "Version": "0.29.0", + "Size": "103509" + } + ], + "Info2": [ + { + "Version": "0.29.0", + "Size": "137451" + } + ], + "Package": "wheel" + }, + { + "Info1": [ + { + "Version": "2.0.0", + "Size": "504226" + } + ], + "Info2": [ + { + "Version": "0.8.0", + "Size": "73348" + } + ], + "Package": "mock" + } + ] } - ] } - } -] +] \ No newline at end of file diff --git a/tests/multi_hist_diff_expected.json b/tests/multi_hist_diff_expected.json deleted file mode 100644 index be0e81cc..00000000 --- a/tests/multi_hist_diff_expected.json +++ /dev/null @@ -1,73 +0,0 @@ -[ - { - "DiffType": "HistoryDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/pip-base", - "Image2": "gcr.io/gcp-runtimes/pip-modified", - "Adds": [ - "/bin/bash" - ], - "Dels": [] - } - }, - { - "DiffType": "PipDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/pip-base", - "Packages1": {}, - "Image2": "gcr.io/gcp-runtimes/pip-modified", - "Packages2": { - "mock": { - "/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" - } - }, - "six": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "1.10.0", - "Size": "30098" - } - } - }, - "InfoDiff": [ - { - "Package": "pip", - "Info1": [ - { - "Version": "9.0.1", - "Size": "3741310" - } - ], - "Info2": [ - { - "Version": "9.0.1", - "Size": "5289421" - } - ] - }, - { - "Package": "wheel", - "Info1": [ - { - "Version": "0.29.0", - "Size": "103348" - } - ], - "Info2": [ - { - "Version": "0.29.0", - "Size": "103509" - } - ] - } - ] - } - } -] diff --git a/tests/node_analysis_expected.json b/tests/node_analysis_expected.json new file mode 100644 index 00000000..10c3d720 --- /dev/null +++ b/tests/node_analysis_expected.json @@ -0,0 +1,26 @@ +[ + { + "Image": "gcr.io/gcp-runtimes/node-modified", + "AnalyzeType": "Node", + "Analysis": { + "npm": { + "/usr/local/lib/node_modules/npm/": { + "Version": "5.0.3", + "Size": "13830218" + } + }, + "pax": { + "/node_modules/pax/": { + "Version": "0.2.1", + "Size": "11365" + } + }, + "sax": { + "/node_modules/sax/": { + "Version": "0.1.1", + "Size": "127390" + } + } + } + } +] \ No newline at end of file diff --git a/tests/node_diff_expected.json b/tests/node_diff_expected.json deleted file mode 100644 index e6ac12ce..00000000 --- a/tests/node_diff_expected.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "DiffType": "NodeDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/node-base", - "Packages1": {}, - "Image2": "gcr.io/gcp-runtimes/node-modified", - "Packages2": { - "pax": { - "/node_modules/pax/": { - "Version": "0.2.1", - "Size": "11365" - } - } - }, - "InfoDiff": [ - { - "Package": "sax", - "Info1": [ - { - "Version": "1.2.4", - "Size": "55471" - } - ], - "Info2": [ - { - "Version": "0.1.1", - "Size": "127390" - } - ] - } - ] - } - } -] diff --git a/tests/node_diff_order_expected.json b/tests/node_diff_order_expected.json index 5be9b332..fe885282 100644 --- a/tests/node_diff_order_expected.json +++ b/tests/node_diff_order_expected.json @@ -1,10 +1,10 @@ [ { - "DiffType": "NodeDiffer", + "Image1": "gcr.io/gcp-runtimes/node-modified:2.0", + "Image2": "gcr.io/gcp-runtimes/node-modified", + "DiffType": "Node", "Diff": { - "Image1": "gcr.io/gcp-runtimes/node-modified:2.0", "Packages1": {}, - "Image2": "gcr.io/gcp-runtimes/node-modified", "Packages2": { "pax": { "/node_modules/pax/": { diff --git a/tests/pip_analysis_expected.json b/tests/pip_analysis_expected.json new file mode 100644 index 00000000..87a54c04 --- /dev/null +++ b/tests/pip_analysis_expected.json @@ -0,0 +1,44 @@ +[ + { + "Image": "gcr.io/gcp-runtimes/pip-modified", + "AnalyzeType": "Pip", + "Analysis": { + "mock": { + "/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" + } + }, + "pip": { + "/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" + } + }, + "six": { + "/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" + } + } + } + } +] \ No newline at end of file diff --git a/tests/pip_diff_expected.json b/tests/pip_diff_expected.json deleted file mode 100644 index 481b5033..00000000 --- a/tests/pip_diff_expected.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "DiffType": "PipDiffer", - "Diff": { - "Image1": "gcr.io/gcp-runtimes/pip-base", - "Packages1": {}, - "Image2": "gcr.io/gcp-runtimes/pip-modified", - "Packages2": { - "mock": { - "/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" - } - }, - "six": { - "/usr/local/lib/python3.6/site-packages": { - "Version": "1.10.0", - "Size": "30098" - } - } - }, - "InfoDiff": [ - { - "Package": "wheel", - "Info1": [ - { - "Version": "0.29.0", - "Size": "103348" - } - ], - "Info2": [ - { - "Version": "0.29.0", - "Size": "103509" - } - ] - }, - { - "Package": "pip", - "Info1": [ - { - "Version": "9.0.1", - "Size": "3741310" - } - ], - "Info2": [ - { - "Version": "9.0.1", - "Size": "5289421" - } - ] - } - ] - } - } -] diff --git a/utils/analyze_output_utils.go b/utils/analyze_output_utils.go new file mode 100644 index 00000000..9296d882 --- /dev/null +++ b/utils/analyze_output_utils.go @@ -0,0 +1,48 @@ +package utils + +type AnalyzeResult interface { + GetStruct() AnalyzeResult + OutputText(analyzeType string) error +} + +type ListAnalyzeResult struct { + Image string + AnalyzeType string + Analysis []string +} + +func (r ListAnalyzeResult) GetStruct() AnalyzeResult { + return r +} + +func (r ListAnalyzeResult) OutputText(analyzeType string) error { + return TemplateOutput(r) +} + +type MultiVersionPackageAnalyzeResult struct { + Image string + AnalyzeType string + Analysis map[string]map[string]PackageInfo +} + +func (r MultiVersionPackageAnalyzeResult) GetStruct() AnalyzeResult { + return r +} + +func (r MultiVersionPackageAnalyzeResult) OutputText(analyzeType string) error { + return TemplateOutput(r) +} + +type SingleVersionPackageAnalyzeResult struct { + Image string + AnalyzeType string + Analysis map[string]PackageInfo +} + +func (r SingleVersionPackageAnalyzeResult) GetStruct() AnalyzeResult { + return r +} + +func (r SingleVersionPackageAnalyzeResult) OutputText(diffType string) error { + return TemplateOutput(r) +} diff --git a/utils/diff_output_utils.go b/utils/diff_output_utils.go new file mode 100644 index 00000000..16aa915f --- /dev/null +++ b/utils/diff_output_utils.go @@ -0,0 +1,66 @@ +package utils + +type DiffResult interface { + GetStruct() DiffResult + OutputText(diffType string) error +} + +type MultiVersionPackageDiffResult struct { + Image1 string + Image2 string + DiffType string + Diff MultiVersionPackageDiff +} + +func (r MultiVersionPackageDiffResult) GetStruct() DiffResult { + return r +} + +func (r MultiVersionPackageDiffResult) OutputText(diffType string) error { + return TemplateOutput(r) +} + +type SingleVersionPackageDiffResult struct { + Image1 string + Image2 string + DiffType string + Diff PackageDiff +} + +func (r SingleVersionPackageDiffResult) GetStruct() DiffResult { + return r +} + +func (r SingleVersionPackageDiffResult) OutputText(diffType string) error { + return TemplateOutput(r) +} + +type HistDiffResult struct { + Image1 string + Image2 string + DiffType string + Diff HistDiff +} + +func (r HistDiffResult) GetStruct() DiffResult { + return r +} + +func (r HistDiffResult) OutputText(diffType string) error { + return TemplateOutput(r) +} + +type DirDiffResult struct { + Image1 string + Image2 string + DiffType string + Diff DirDiff +} + +func (r DirDiffResult) GetStruct() DiffResult { + return r +} + +func (r DirDiffResult) OutputText(diffType string) error { + return TemplateOutput(r) +} diff --git a/utils/docker_utils.go b/utils/docker_utils.go index 829b865d..aec90a7b 100644 --- a/utils/docker_utils.go +++ b/utils/docker_utils.go @@ -110,10 +110,8 @@ func pullImageFromRepo(image string) (string, string, error) { } type HistDiff struct { - Image1 string - Image2 string - Adds []string - Dels []string + Adds []string + Dels []string } // getImageHistory shells out the docker history command and returns a list of history response items. diff --git a/utils/format_utils.go b/utils/format_utils.go index 4b6e6a6b..c36f396b 100644 --- a/utils/format_utils.go +++ b/utils/format_utils.go @@ -3,7 +3,7 @@ package utils import ( "bufio" "encoding/json" - "fmt" + "errors" "html/template" "os" "reflect" @@ -14,10 +14,13 @@ import ( ) var templates = map[string]string{ - "utils.PackageDiffResult": SingleVersionOutput, - "utils.MultiVersionPackageDiffResult": MultiVersionOutput, - "utils.HistDiffResult": HistoryOutput, - "utils.DirDiffResult": FSOutput, + "utils.SingleVersionPackageDiffResult": SingleVersionDiffOutput, + "utils.MultiVersionPackageDiffResult": MultiVersionDiffOutput, + "utils.HistDiffResult": HistoryDiffOutput, + "utils.DirDiffResult": FSDiffOutput, + "utils.ListAnalyzeResult": ListAnalysisOutput, + "utils.MultiVersionPackageAnalyzeResult": MultiVersionPackageOutput, + "utils.SingleVersionPackageAnalyzeResult": SingleVersionPackageOutput, } func JSONify(diff interface{}) error { @@ -36,7 +39,7 @@ func getTemplate(diff interface{}) (string, error) { if template, ok := templates[diffType]; ok { return template, nil } - return "", fmt.Errorf("No available template") + return "", errors.New("No available template") } func TemplateOutput(diff interface{}) error { diff --git a/utils/fs_utils.go b/utils/fs_utils.go index ee6b69c1..2e64dc5b 100644 --- a/utils/fs_utils.go +++ b/utils/fs_utils.go @@ -107,11 +107,9 @@ func GetDeletedEntries(d1, d2 Directory) []string { } type DirDiff struct { - Image1 string - Image2 string - Adds []string - Dels []string - Mods []string + Adds []string + Dels []string + Mods []string } // DiffDirectory takes the diff of two directories, assuming both are completely unpacked @@ -127,7 +125,7 @@ func DiffDirectory(d1, d2 Directory) (DirDiff, bool) { same = false } - return DirDiff{d1.Root, d2.Root, adds, dels, mods}, same + return DirDiff{adds, dels, mods}, same } func checkSameFile(f1name, f2name string) (bool, error) { diff --git a/utils/output_utils.go b/utils/output_utils.go deleted file mode 100644 index bc1b0bcc..00000000 --- a/utils/output_utils.go +++ /dev/null @@ -1,58 +0,0 @@ -package utils - -type DiffResult interface { - GetStruct() DiffResult - OutputText(diffType string) error -} - -type MultiVersionPackageDiffResult struct { - DiffType string - Diff MultiVersionPackageDiff -} - -func (m MultiVersionPackageDiffResult) GetStruct() DiffResult { - return m -} - -func (m MultiVersionPackageDiffResult) OutputText(diffType string) error { - return TemplateOutput(m) -} - -type PackageDiffResult struct { - DiffType string - Diff PackageDiff -} - -func (m PackageDiffResult) GetStruct() DiffResult { - return m -} - -func (m PackageDiffResult) OutputText(diffType string) error { - return TemplateOutput(m) -} - -type HistDiffResult struct { - DiffType string - Diff HistDiff -} - -func (m HistDiffResult) GetStruct() DiffResult { - return m -} - -func (m HistDiffResult) OutputText(diffType string) error { - return TemplateOutput(m) -} - -type DirDiffResult struct { - DiffType string - Diff DirDiff -} - -func (m DirDiffResult) GetStruct() DiffResult { - return m -} - -func (m DirDiffResult) OutputText(diffType string) error { - return TemplateOutput(m) -} diff --git a/utils/package_diff_utils.go b/utils/package_diff_utils.go index d54b49c3..044e3ed8 100644 --- a/utils/package_diff_utils.go +++ b/utils/package_diff_utils.go @@ -11,9 +11,7 @@ import ( // MultiVersionPackageDiff stores the difference information between two images which could have multi-version packages. type MultiVersionPackageDiff struct { - Image1 string Packages1 map[string]map[string]PackageInfo - Image2 string Packages2 map[string]map[string]PackageInfo InfoDiff []MultiVersionInfo } @@ -27,9 +25,7 @@ type MultiVersionInfo struct { // PackageDiff stores the difference information between two images. type PackageDiff struct { - Image1 string Packages1 map[string]PackageInfo - Image2 string Packages2 map[string]PackageInfo InfoDiff []Info } @@ -95,24 +91,20 @@ func checkPackageMapType(map1, map2 interface{}) (reflect.Type, bool, error) { // GetMapDiff determines the differences between maps of package names to PackageInfo structs // This getter supports only single version packages. -func GetMapDiff(map1, map2 map[string]PackageInfo, img1, img2 string) PackageDiffResult { +func GetMapDiff(map1, map2 map[string]PackageInfo) PackageDiff { diff := diffMaps(map1, map2) diffVal := reflect.ValueOf(diff) packDiff := diffVal.Interface().(PackageDiff) - packDiff.Image1 = img1 - packDiff.Image2 = img2 - return PackageDiffResult{Diff: packDiff} + return packDiff } // GetMultiVersionMapDiff determines the differences between two image package maps with multi-version packages // This getter supports multi version packages. -func GetMultiVersionMapDiff(map1, map2 map[string]map[string]PackageInfo, img1, img2 string) MultiVersionPackageDiffResult { +func GetMultiVersionMapDiff(map1, map2 map[string]map[string]PackageInfo) MultiVersionPackageDiff { diff := diffMaps(map1, map2) diffVal := reflect.ValueOf(diff) packDiff := diffVal.Interface().(MultiVersionPackageDiff) - packDiff.Image1 = img1 - packDiff.Image2 = img2 - return MultiVersionPackageDiffResult{Diff: packDiff} + return packDiff } // DiffMaps determines the differences between maps of package names to PackageInfo structs diff --git a/utils/template_utils.go b/utils/template_utils.go index 7494aa09..57d32623 100644 --- a/utils/template_utils.go +++ b/utils/template_utils.go @@ -1,51 +1,71 @@ package utils -const FSOutput = ` +const FSDiffOutput = ` -----{{.DiffType}}----- -These entries have been added to {{.Diff.Image1}}:{{if not .Diff.Adds}} None{{else}} +These entries have been added to {{.Image1}}:{{if not .Diff.Adds}} None{{else}} {{range .Diff.Adds}}{{print .}} {{end}}{{end}} -These entries have been deleted from {{.Diff.Image1}}:{{if not .Diff.Dels}} None{{else}} +These entries have been deleted from {{.Image1}}:{{if not .Diff.Dels}} None{{else}} {{range .Diff.Dels}}{{print .}} {{end}}{{end}} -These entries have been changed between {{.Diff.Image1}} and {{.Diff.Image2}}:{{if not .Diff.Mods}} None{{else}} +These entries have been changed between {{.Image1}} and {{.Image2}}:{{if not .Diff.Mods}} None{{else}} {{range .Diff.Mods}}{{print .}} {{end}}{{end}} ` -const SingleVersionOutput = ` +const SingleVersionDiffOutput = ` -----{{.DiffType}}----- -Packages found only in {{.Diff.Image1}}:{{if not .Diff.Packages1}} None{{else}} +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}}B{{end}}{{end}} -Packages found only in {{.Diff.Image2}}:{{if not .Diff.Packages2}} None{{else}} +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}}B{{end}}{{end}} Version differences:{{if not .Diff.InfoDiff}} None{{else}} -PACKAGE IMAGE1 ({{.Diff.Image1}}) IMAGE2 ({{.Diff.Image2}}){{range .Diff.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{.Info1.Version}}, {{.Info1.Size}}B {{.Info2.Version}}, {{.Info2.Size}}B{{end}}{{end}} +PACKAGE IMAGE1 ({{.Image1}}) IMAGE2 ({{.Image2}}){{range .Diff.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{.Info1.Version}}, {{.Info1.Size}}B {{.Info2.Version}}, {{.Info2.Size}}B{{end}}{{end}} ` -const MultiVersionOutput = ` +const MultiVersionDiffOutput = ` -----{{.DiffType}}----- -Packages found only in {{.Diff.Image1}}:{{if not .Diff.Packages1}} None{{else}} +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}}B{{end}}{{end}}{{end}} -Packages found only in {{.Diff.Image2}}:{{if not .Diff.Packages2}} None{{else}} +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}}B{{end}}{{end}}{{end}} Version differences:{{if not .Diff.InfoDiff}} None{{else}} -PACKAGE IMAGE1 ({{.Diff.Image1}}) IMAGE2 ({{.Diff.Image2}}){{range .Diff.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{range .Info1}}{{.Version}}, {{.Size}}B{{end}} {{range .Info2}}{{.Version}}, {{.Size}}B{{end}}{{end}}{{end}} +PACKAGE IMAGE1 ({{.Image1}}) IMAGE2 ({{.Image2}}){{range .Diff.InfoDiff}}{{"\n"}}{{print "-"}}{{.Package}} {{range .Info1}}{{.Version}}, {{.Size}}B{{end}} {{range .Info2}}{{.Version}}, {{.Size}}B{{end}}{{end}}{{end}} ` -const HistoryOutput = ` +const HistoryDiffOutput = ` -----{{.DiffType}}----- -Docker history lines found only in {{.Diff.Image1}}:{{if not .Diff.Adds}} None{{else}}{{block "list" .Diff.Adds}}{{"\n"}}{{range .}}{{print "-" .}}{{end}}{{end}}{{end}} +Docker history lines found only in {{.Image1}}:{{if not .Diff.Adds}} None{{else}}{{block "list" .Diff.Adds}}{{"\n"}}{{range .}}{{print "-" .}}{{"\n"}}{{end}}{{end}}{{end}} -Docker history lines found only in {{.Diff.Image2}}:{{if not .Diff.Dels}} None{{else}}{{block "list2" .Diff.Dels}}{{"\n"}}{{range .}}{{print "-" .}}{{end}}{{end}}{{end}} +Docker history lines found only in {{.Image2}}:{{if not .Diff.Dels}} None{{else}}{{block "list2" .Diff.Dels}}{{"\n"}}{{range .}}{{print "-" .}}{{"\n"}}{{end}}{{end}}{{end}} +` + +const ListAnalysisOutput = ` +-----{{.AnalyzeType}}----- + +Analysis for {{.Image}}:{{if not .Analysis}} None{{else}}{{block "list" .Analysis}}{{"\n"}}{{range .}}{{print "-" .}}{{"\n"}}{{end}}{{end}}{{end}} +` + +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}}B{{end}}{{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}}B{{end}}{{end}} `