From c8a72670ad6fc4aa09f1319f6d7983b9bf353424 Mon Sep 17 00:00:00 2001 From: Aaron Prindle Date: Thu, 31 Aug 2017 13:27:47 -0700 Subject: [PATCH] Changed CLI to use --types flag, a comma separated list of desired analyzers --- README.md | 30 ++++++++--------- cmd/analyze.go | 27 ++++++--------- cmd/analyze_test.go | 4 +-- cmd/diff.go | 26 +++++---------- cmd/diff_test.go | 4 +-- cmd/root.go | 70 ++++++++++++++------------------------- cmd/root_test.go | 4 +-- differs/differs.go | 4 +-- tests/integration_test.go | 24 +++++++------- 9 files changed, 78 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index d9c73aa0..24e2f0a9 100644 --- a/README.md +++ b/README.md @@ -43,27 +43,29 @@ To use `container-diff analyze` to perform analysis on a single image, you need ``` container-diff analyze [Run all analyzers] -container-diff analyze -d [History] -container-diff analyze -f [File System] -container-diff analyze -p [Pip] -container-diff analyze -a [Apt] -container-diff analyze -n [Node] +container-diff analyze --types=history [History] +container-diff analyze --types=file [File System] +container-diff analyze --types=pip [Pip] +container-diff analyze --types=apt [Apt] +container-diff analyze --types=node [Node] +container-diff analyze --types=apt,node [Apt and Node] +# --types=,,,... ``` 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 diff [Run all differs] -container-diff diff -d [History] -container-diff diff -f [File System] -container-diff diff -p [Pip] -container-diff diff -a [Apt] -container-diff diff -n [Node] +container-diff diff --types=history [History] +container-diff diff --types=file [File System] +container-diff diff --types=pip [Pip] +container-diff diff --types=apt [Apt] +container-diff diff --types=node [Node] ``` You can similarly run many analyzers at once: ``` -container-diff diff -d -a -n [History, Apt, and Node] +container-diff diff --types=history,apt,node [History, Apt, and Node] ``` All of the analyzer flags with their long versions can be seen below: @@ -228,7 +230,7 @@ To run container-diff on image IDs, docker must be installed. ## Example Run ``` -$ container-diff gcr.io/google-appengine/python:2017-07-21-123058 gcr.io/google-appengine/python:2017-06-29-190410 -a -n -p +$ container-diff diff gcr.io/google-appengine/python:2017-07-21-123058 gcr.io/google-appengine/python:2017-06-29-190410 --types=apt,node,pip -----AptDiffer----- @@ -372,7 +374,3 @@ This is where you define how your analyzer should output for a human readable fo 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/analyze.go b/cmd/analyze.go index 8ea3a5d7..55c877b5 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strings" "github.com/GoogleCloudPlatform/container-diff/differs" "github.com/GoogleCloudPlatform/container-diff/utils" @@ -16,37 +17,29 @@ var analyzeCmd = &cobra.Command{ Short: "Analyzes an image: [image]", Long: `Analyzes an image using the specifed analyzers as indicated via flags (see documentation for available ones).`, Run: func(cmd *cobra.Command, args []string) { - if validArgs, err := validateArgs(args, checkAnalyzeArgNum, checkArgType); !validArgs { + if err := validateArgs(args, checkAnalyzeArgNum, checkArgType); err != nil { glog.Error(err.Error()) os.Exit(1) } - analyzeArgs := []string{} - allAnalyzers := getAllAnalyzers() - for _, name := range allAnalyzers { - if *analyzeFlagMap[name] == true { - analyzeArgs = append(analyzeArgs, name) - } - } - - // If no analyzers are specified, perform them all as the default - if len(analyzeArgs) == 0 { - analyzeArgs = allAnalyzers + if err := checkIfValidAnalyzer(types); err != nil { + glog.Error(err) + os.Exit(1) } - - if err := analyzeImage(args[0], analyzeArgs); err != nil { + if err := analyzeImage(args[0], strings.Split(types, ",")); err != nil { glog.Error(err) os.Exit(1) } }, } -func checkAnalyzeArgNum(args []string) (bool, error) { +func checkAnalyzeArgNum(args []string) error { var errMessage string if len(args) != 1 { errMessage = "'analyze' requires one image as an argument: container analyze [image]" - return false, errors.New(errMessage) + glog.Errorf(errMessage) + return errors.New(errMessage) } - return true, nil + return nil } func analyzeImage(imageArg string, analyzerArgs []string) error { diff --git a/cmd/analyze_test.go b/cmd/analyze_test.go index 8c74de1f..3f429f21 100644 --- a/cmd/analyze_test.go +++ b/cmd/analyze_test.go @@ -12,8 +12,8 @@ var analyzeArgNumTests = []testpair{ func TestAnalyzeArgNum(t *testing.T) { for _, test := range analyzeArgNumTests { - valid, err := checkAnalyzeArgNum(test.input) - if valid != test.expected_output { + err := checkAnalyzeArgNum(test.input) + if (err == nil) != test.expected_output { if test.expected_output { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/cmd/diff.go b/cmd/diff.go index f8124fc9..fdcab75c 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strings" "sync" "github.com/GoogleCloudPlatform/container-diff/differs" @@ -17,37 +18,28 @@ var diffCmd = &cobra.Command{ Short: "Compare two images: [image1] [image2]", Long: `Compares two images using the specifed analyzers as indicated via flags (see documentation for available ones).`, Run: func(cmd *cobra.Command, args []string) { - if validArgs, err := validateArgs(args, checkDiffArgNum, checkArgType); !validArgs { + if err := validateArgs(args, checkDiffArgNum, checkArgType); err != nil { glog.Error(err.Error()) os.Exit(1) } - analyzeArgs := []string{} - allAnalyzers := getAllAnalyzers() - for _, name := range allAnalyzers { - if *analyzeFlagMap[name] == true { - analyzeArgs = append(analyzeArgs, name) - } - } - - // If no analyzers are specified, perform them all as the default - if len(analyzeArgs) == 0 { - analyzeArgs = allAnalyzers + if err := checkIfValidAnalyzer(types); err != nil { + glog.Error(err) + os.Exit(1) } - - if err := diffImages(args[0], args[1], analyzeArgs); err != nil { + if err := diffImages(args[0], args[1], strings.Split(types, ",")); err != nil { glog.Error(err) os.Exit(1) } }, } -func checkDiffArgNum(args []string) (bool, error) { +func checkDiffArgNum(args []string) error { var errMessage string if len(args) != 2 { errMessage = "'diff' requires two images as arguments: container diff [image1] [image2]" - return false, errors.New(errMessage) + return errors.New(errMessage) } - return true, nil + return nil } func diffImages(image1Arg, image2Arg string, diffArgs []string) error { diff --git a/cmd/diff_test.go b/cmd/diff_test.go index af1cdda7..bd7fd3b2 100644 --- a/cmd/diff_test.go +++ b/cmd/diff_test.go @@ -13,8 +13,8 @@ var diffArgNumTests = []testpair{ func TestDiffArgNum(t *testing.T) { for _, test := range diffArgNumTests { - valid, err := checkDiffArgNum(test.input) - if valid != test.expected_output { + err := checkDiffArgNum(test.input) + if (err == nil) != test.expected_output { if test.expected_output { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/cmd/root.go b/cmd/root.go index 35928353..e031d9c4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,40 +1,28 @@ package cmd import ( - "bytes" "context" - "errors" goflag "flag" "fmt" "os" "reflect" "sort" + "strings" + "github.com/GoogleCloudPlatform/container-diff/differs" "github.com/GoogleCloudPlatform/container-diff/utils" "github.com/docker/docker/client" "github.com/golang/glog" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var json bool -var eng bool var save bool -var apt bool -var node bool -var file bool -var history bool -var pip bool - -var analyzeFlagMap = map[string]*bool{ - "apt": &apt, - "node": &node, - "file": &file, - "history": &history, - "pip": &pip, -} +var types string -type validatefxn func(args []string) (bool, error) +type validatefxn func(args []string) error var RootCmd = &cobra.Command{ Use: "container-diff", @@ -90,24 +78,13 @@ func cleanupImage(image utils.Image) { } } -func getAllAnalyzers() []string { - allAnalyzers := []string{} - for name := range analyzeFlagMap { - allAnalyzers = append(allAnalyzers, name) - } - return allAnalyzers -} - -func validateArgs(args []string, validatefxns ...validatefxn) (bool, error) { +func validateArgs(args []string, validatefxns ...validatefxn) error { for _, validatefxn := range validatefxns { - valid, err := validatefxn(args) - if err != nil { - return false, err - } else if !valid { - return false, nil + if err := validatefxn(args); err != nil { + return err } } - return true, nil + return nil } func checkImage(arg string) bool { @@ -117,20 +94,27 @@ func checkImage(arg string) bool { return true } -func checkArgType(args []string) (bool, error) { - var buffer bytes.Buffer - valid := true +func checkArgType(args []string) error { 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) + glog.Errorf(errMessage) + return errors.New(errMessage) } } - if !valid { - return false, errors.New(buffer.String()) + return nil +} + +func checkIfValidAnalyzer(flagtypes string) error { + analyzers := strings.Split(flagtypes, ",") + for _, name := range analyzers { + if _, exists := differs.Analyzers[name]; !exists { + errMessage := fmt.Sprintf("Argument %s is not an image ID, URL, or tar\n", name) + glog.Errorf(errMessage) + return errors.New(errMessage) + } } - return true, nil + return nil } func remove(path string, dir bool) string { @@ -157,11 +141,7 @@ func init() { func addSharedFlags(cmd *cobra.Command) { cmd.Flags().BoolVarP(&json, "json", "j", false, "JSON Output defines if the diff should be returned in a human readable format (false) or a JSON (true).") - cmd.Flags().BoolVarP(&pip, "pip", "p", false, "Set this flag to use the pip differ.") - cmd.Flags().BoolVarP(&node, "node", "n", false, "Set this flag to use the node differ.") - cmd.Flags().BoolVarP(&apt, "apt", "a", false, "Set this flag to use the apt differ.") - cmd.Flags().BoolVarP(&file, "file", "f", false, "Set this flag to use the file differ.") - cmd.Flags().BoolVarP(&history, "history", "d", false, "Set this flag to use the dockerfile history differ.") + cmd.Flags().StringVarP(&types, "types", "t", "", "This flag sets the list of analyzer types to use. It expects a comma separated list of supported analyzers.") cmd.Flags().BoolVarP(&save, "save", "s", false, "Set this flag to save rather than remove the final image filesystems on exit.") cmd.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/cmd/root_test.go b/cmd/root_test.go index 0a5ae772..0821e1e9 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -19,8 +19,8 @@ var argTypeTests = []testpair{ func TestArgType(t *testing.T) { for _, test := range argTypeTests { - valid, err := checkArgType(test.input) - if valid != test.expected_output { + err := checkArgType(test.input) + if (err == nil) != test.expected_output { if test.expected_output { t.Errorf("Got unexpected error: %s", err) } else { diff --git a/differs/differs.go b/differs/differs.go index 976b12b4..65a96896 100644 --- a/differs/differs.go +++ b/differs/differs.go @@ -25,7 +25,7 @@ type Analyzer interface { Analyze(image utils.Image) (utils.Result, error) } -var analyzers = map[string]Analyzer{ +var Analyzers = map[string]Analyzer{ "history": HistoryAnalyzer{}, "file": FileAnalyzer{}, "apt": AptAnalyzer{}, @@ -84,7 +84,7 @@ func (req SingleRequest) GetAnalysis() (map[string]utils.Result, error) { func GetAnalyzers(analyzeNames []string) (analyzeFuncs []Analyzer, err error) { for _, name := range analyzeNames { - if a, exists := analyzers[name]; exists { + if a, exists := Analyzers[name]; exists { analyzeFuncs = append(analyzeFuncs, a) } else { glog.Errorf("Unknown analyzer/differ specified", name) diff --git a/tests/integration_test.go b/tests/integration_test.go index da33a8f3..82fb0e48 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -61,7 +61,7 @@ func TestDiffAndAnalysis(t *testing.T) { description string imageA string imageB string - differFlag string + differFlags []string subcommand string //TODO: Don't consume a json file @@ -72,7 +72,7 @@ func TestDiffAndAnalysis(t *testing.T) { subcommand: "diff", imageA: diffBase, imageB: diffModified, - differFlag: "-f", + differFlags: []string{"--types=file"}, expectedFile: "file_diff_expected.json", }, { @@ -80,7 +80,7 @@ func TestDiffAndAnalysis(t *testing.T) { subcommand: "diff", imageA: aptBase, imageB: aptModified, - differFlag: "-a", + differFlags: []string{"--types=apt"}, expectedFile: "apt_diff_expected.json", }, { @@ -88,7 +88,7 @@ func TestDiffAndAnalysis(t *testing.T) { subcommand: "diff", imageA: nodeBase, imageB: nodeModified, - differFlag: "-n", + differFlags: []string{"--types=node"}, expectedFile: "node_diff_order_expected.json", }, { @@ -96,7 +96,7 @@ func TestDiffAndAnalysis(t *testing.T) { subcommand: "diff", imageA: multiBase, imageB: multiModified, - differFlag: "-npa", + differFlags: []string{"--types=node,pip,apt"}, expectedFile: "multi_diff_expected.json", }, { @@ -104,7 +104,7 @@ func TestDiffAndAnalysis(t *testing.T) { subcommand: "diff", imageA: diffBase, imageB: diffModified, - differFlag: "-d", + differFlags: []string{"--types=history"}, expectedFile: "hist_diff_expected.json", }, { @@ -112,35 +112,35 @@ func TestDiffAndAnalysis(t *testing.T) { subcommand: "diff", imageA: aptBase, imageB: aptModified, - differFlag: "-ao", + differFlags: []string{"--types=apt", "-o"}, expectedFile: "apt_sorted_diff_expected.json", }, { description: "apt analysis", subcommand: "analyze", imageA: aptModified, - differFlag: "-a", + differFlags: []string{"--types=apt"}, expectedFile: "apt_analysis_expected.json", }, { description: "file sorted analysis", subcommand: "analyze", imageA: diffModified, - differFlag: "-fo", + differFlags: []string{"--types=file", "-o"}, expectedFile: "file_sorted_analysis_expected.json", }, { description: "pip analysis", subcommand: "analyze", imageA: pipModified, - differFlag: "-p", + differFlags: []string{"--types=pip"}, expectedFile: "pip_analysis_expected.json", }, { description: "node analysis", subcommand: "analyze", imageA: nodeModified, - differFlag: "-n", + differFlags: []string{"--types=node"}, expectedFile: "node_analysis_expected.json", }, } @@ -150,7 +150,7 @@ func TestDiffAndAnalysis(t *testing.T) { if test.imageB != "" { args = append(args, test.imageB) } - args = append(args, test.differFlag) + args = append(args, test.differFlags...) args = append(args, "-j") actual, err := runner.Run(args...) if err != nil {