From 276ed9e7ee25f7954f20b402abad74e17505e811 Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Fri, 18 Dec 2020 15:35:22 +0530 Subject: [PATCH 1/6] fix too many arguments for Run func --- pkg/cli/run.go | 14 ++++----- pkg/cli/run_test.go | 70 ++++++++++++++++++++++++--------------------- pkg/cli/scan.go | 42 ++++++++++++++------------- 3 files changed, 66 insertions(+), 60 deletions(-) diff --git a/pkg/cli/run.go b/pkg/cli/run.go index 629f26603..b40c73402 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -35,27 +35,25 @@ const ( ) // Run executes terrascan in CLI mode -func Run(iacType, iacVersion string, cloudType []string, - iacFilePath, iacDirPath, configFile string, policyPath []string, - format, remoteType, remoteURL string, configOnly, useColors, verbose bool) { +func Run(configFile, format string, scanStruct *ScanOptions) { // temp dir to download the remote repo tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) defer os.RemoveAll(tempDir) // download remote repository - path, err := downloadRemoteRepository(remoteType, remoteURL, tempDir) + path, err := downloadRemoteRepository(scanStruct.RemoteType, scanStruct.RemoteURL, tempDir) if err != nil { return } if path != "" { - iacDirPath = path + scanStruct.IacDirPath = path } // create a new runtime executor for processing IaC - executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType, - iacFilePath, iacDirPath, configFile, policyPath) + executor, err := runtime.NewExecutor(scanStruct.IacType, scanStruct.IacVersion, scanStruct.PolicyType, + scanStruct.IacFilePath, scanStruct.IacDirPath, configFile, scanStruct.PolicyPath) if err != nil { return } @@ -67,7 +65,7 @@ func Run(iacType, iacVersion string, cloudType []string, } // write results to console - err = writeResults(results, useColors, verbose, configOnly, format) + err = writeResults(results, scanStruct.UseColors, scanStruct.Verbose, scanStruct.ConfigOnly, format) if err != nil { zap.S().Error("failed to write results", zap.Error(err)) return diff --git a/pkg/cli/run_test.go b/pkg/cli/run_test.go index 589f2d453..312a11dd5 100644 --- a/pkg/cli/run_test.go +++ b/pkg/cli/run_test.go @@ -31,59 +31,65 @@ import ( func TestRun(t *testing.T) { table := []struct { name string - iacType string - iacVersion string - cloudType []string - format string - iacFilePath string - iacDirPath string configFile string - configOnly bool - verbose bool + format string + scanOptions *ScanOptions stdOut string want string wantErr error }{ { - name: "normal terraform run", - cloudType: []string{"terraform"}, - iacDirPath: "testdata/run-test", + name: "normal terraform run", + scanOptions: &ScanOptions{ + PolicyType: []string{"terraform"}, + IacDirPath: "testdata/run-test", + }, }, { - name: "normal k8s run", - cloudType: []string{"k8s"}, - iacDirPath: "testdata/run-test", + name: "normal k8s run", + scanOptions: &ScanOptions{ + PolicyType: []string{"k8s"}, + IacDirPath: "testdata/run-test", + }, }, { - name: "config-only flag terraform", - cloudType: []string{"terraform"}, - iacFilePath: "testdata/run-test/config-only.tf", - configOnly: true, + name: "config-only flag terraform", + scanOptions: &ScanOptions{ + PolicyType: []string{"terraform"}, + IacDirPath: "testdata/run-test/config-only.tf", + ConfigOnly: true, + }, }, { - name: "config-only flag k8s", - cloudType: []string{"k8s"}, - iacFilePath: "testdata/run-test/config-only.yaml", - configOnly: true, + name: "config-only flag k8s", + scanOptions: &ScanOptions{ + PolicyType: []string{"k8s"}, + IacDirPath: "testdata/run-test/config-only.yaml", + ConfigOnly: true, + }, }, { - name: "config-only flag true with human readable format", - cloudType: []string{"terraform"}, - iacFilePath: "testdata/run-test/config-only.tf", - configOnly: true, - format: "human", + name: "config-only flag true with human readable format", + scanOptions: &ScanOptions{ + PolicyType: []string{"terraform"}, + IacDirPath: "testdata/run-test/config-only.tf", + ConfigOnly: true, + }, + format: "human", }, { - name: "config-only flag false with human readable format", - cloudType: []string{"k8s"}, - iacFilePath: "testdata/run-test/config-only.yaml", - format: "human", + name: "config-only flag false with human readable format", + scanOptions: &ScanOptions{ + PolicyType: []string{"k8s"}, + IacDirPath: "testdata/run-test/config-only.yaml", + }, + format: "human", }, } for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - Run(tt.iacType, tt.iacVersion, tt.cloudType, tt.iacFilePath, tt.iacDirPath, tt.configFile, []string{}, tt.format, "", "", tt.configOnly, false, tt.verbose) + Run(tt.configFile, tt.format, scanOptions) }) } } diff --git a/pkg/cli/scan.go b/pkg/cli/scan.go index 54568ac6a..0f5f9b9bb 100644 --- a/pkg/cli/scan.go +++ b/pkg/cli/scan.go @@ -28,7 +28,8 @@ import ( "go.uber.org/zap" ) -var ( +// ScanOptions represents scan command and its optional flags +type ScanOptions struct { // PolicyPath Policy path directory PolicyPath []string @@ -63,7 +64,9 @@ var ( // Verbose indicates whether to display all fields in default human readlbe output Verbose bool -) +} + +var scanOptions = &ScanOptions{} var scanCmd = &cobra.Command{ Use: "scan", @@ -73,12 +76,12 @@ var scanCmd = &cobra.Command{ Detect compliance and security violations across Infrastructure as Code to mitigate risk before provisioning cloud native infrastructure. `, PreRun: func(cmd *cobra.Command, args []string) { - switch strings.ToLower(useColors) { + switch strings.ToLower(scanOptions.useColors) { case "auto": if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { - UseColors = true + scanOptions.UseColors = true } else { - UseColors = false + scanOptions.UseColors = false } case "true": @@ -90,10 +93,10 @@ Detect compliance and security violations across Infrastructure as Code to mitig case "1": fallthrough case "force": - UseColors = true + scanOptions.UseColors = true default: - UseColors = false + scanOptions.UseColors = false } initial(cmd, args) }, @@ -102,22 +105,21 @@ Detect compliance and security violations across Infrastructure as Code to mitig func scan(cmd *cobra.Command, args []string) { zap.S().Debug("running terrascan in cli mode") - Run(IacType, IacVersion, PolicyType, IacFilePath, IacDirPath, ConfigFile, - PolicyPath, OutputType, RemoteType, RemoteURL, ConfigOnly, UseColors, Verbose) + Run(ConfigFile, OutputType, scanOptions) } func init() { - scanCmd.Flags().StringSliceVarP(&PolicyType, "policy-type", "t", []string{"all"}, fmt.Sprintf("policy type (%s)", strings.Join(policy.SupportedPolicyTypes(true), ", "))) - scanCmd.Flags().StringVarP(&IacType, "iac-type", "i", "", fmt.Sprintf("iac type (%v)", strings.Join(iacProvider.SupportedIacProviders(), ", "))) - scanCmd.Flags().StringVarP(&IacVersion, "iac-version", "", "", fmt.Sprintf("iac version (%v)", strings.Join(iacProvider.SupportedIacVersions(), ", "))) - scanCmd.Flags().StringVarP(&IacFilePath, "iac-file", "f", "", "path to a single IaC file") - scanCmd.Flags().StringVarP(&IacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") - scanCmd.Flags().StringArrayVarP(&PolicyPath, "policy-path", "p", []string{}, "policy path directory") - scanCmd.Flags().StringVarP(&RemoteType, "remote-type", "r", "", "type of remote backend (git, s3, gcs, http)") - scanCmd.Flags().StringVarP(&RemoteURL, "remote-url", "u", "", "url pointing to remote IaC repository") - scanCmd.Flags().BoolVarP(&ConfigOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") + scanCmd.Flags().StringSliceVarP(&scanOptions.PolicyType, "policy-type", "t", []string{"all"}, fmt.Sprintf("policy type (%s)", strings.Join(policy.SupportedPolicyTypes(true), ", "))) + scanCmd.Flags().StringVarP(&scanOptions.IacType, "iac-type", "i", "", fmt.Sprintf("iac type (%v)", strings.Join(iacProvider.SupportedIacProviders(), ", "))) + scanCmd.Flags().StringVarP(&scanOptions.IacVersion, "iac-version", "", "", fmt.Sprintf("iac version (%v)", strings.Join(iacProvider.SupportedIacVersions(), ", "))) + scanCmd.Flags().StringVarP(&scanOptions.IacFilePath, "iac-file", "f", "", "path to a single IaC file") + scanCmd.Flags().StringVarP(&scanOptions.IacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") + scanCmd.Flags().StringArrayVarP(&scanOptions.PolicyPath, "policy-path", "p", []string{}, "policy path directory") + scanCmd.Flags().StringVarP(&scanOptions.RemoteType, "remote-type", "r", "", "type of remote backend (git, s3, gcs, http)") + scanCmd.Flags().StringVarP(&scanOptions.RemoteURL, "remote-url", "u", "", "url pointing to remote IaC repository") + scanCmd.Flags().BoolVarP(&scanOptions.ConfigOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") // flag passes a string, but we normalize to bool in PreRun - scanCmd.Flags().StringVar(&useColors, "use-colors", "auto", "color output (auto, t, f)") - scanCmd.Flags().BoolVarP(&Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)") + scanCmd.Flags().StringVar(&scanOptions.useColors, "use-colors", "auto", "color output (auto, t, f)") + scanCmd.Flags().BoolVarP(&scanOptions.Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)") RegisterCommand(rootCmd, scanCmd) } From bc056a566eb5e5374645214a7d1d8d8b57b1a6e0 Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Sat, 19 Dec 2020 15:50:48 +0530 Subject: [PATCH 2/6] refactor scan command --- pkg/cli/run.go | 162 +++++++++++++++++++----- pkg/cli/run_test.go | 301 ++++++++++++++++++++++++++++++++------------ pkg/cli/scan.go | 96 +++----------- 3 files changed, 369 insertions(+), 190 deletions(-) diff --git a/pkg/cli/run.go b/pkg/cli/run.go index b40c73402..2ef0af854 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -27,6 +27,7 @@ import ( "github.com/accurics/terrascan/pkg/runtime" "github.com/accurics/terrascan/pkg/utils" "github.com/accurics/terrascan/pkg/writer" + "github.com/mattn/go-isatty" "go.uber.org/zap" ) @@ -34,78 +35,177 @@ const ( humanOutputFormat = "human" ) +// ScanCommand represents scan command and its optional flags +type ScanCommand struct { + // Policy path directory + policyPath []string + + // Cloud type (aws, azure, gcp, github) + policyType []string + + // IaC type (terraform) + iacType string + + // IaC version (for terraform:v12) + iacVersion string + + // Path to a single IaC file + iacFilePath string + + // Path to a directory containing one or more IaC files + iacDirPath string + + // remoteType indicates the type of remote backend. Supported backends are + // git s3, gcs, http. + remoteType string + + // remoteURL points to the remote Iac repository on git, s3, gcs, http + remoteURL string + + // configOnly will output resource config (should only be used for debugging purposes) + configOnly bool + + // config file path + configFile string + + // the output format for wring the results + outputType string + + // UseColors indicates whether to use color output + UseColors bool + useColors string // used for flag processing + + // Verbose indicates whether to display all fields in default human readlbe output + Verbose bool +} + +// StartScan starts the terrascan scan command +func (s *ScanCommand) StartScan() error { + err := s.Init() + if err != nil { + return err + } + + err = s.Run() + if err != nil { + return err + } + return nil +} + +//Init initalises and validates ScanCommand +func (s *ScanCommand) Init() error { + s.initColor() + err := s.validate() + if err != nil { + zap.S().Error("failed to start scan", zap.Error(err)) + return err + } + return nil +} + +// validate config only for human readable output +// rest command options are validated by the executor +func (s ScanCommand) validate() error { + // human readable output doesn't support --config-only flag + // if --config-only flag is set, then exit with an error + // asking the user to use yaml or json output format + if s.configOnly && strings.EqualFold(s.outputType, humanOutputFormat) { + return errors.New("please use yaml or json output format when using --config-only flag") + } + return nil +} + +// initialises use colors options +func (s *ScanCommand) initColor() { + switch strings.ToLower(s.useColors) { + case "auto": + if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { + s.UseColors = true + } else { + s.UseColors = false + } + + case "true": + fallthrough + case "t": + fallthrough + case "y": + fallthrough + case "1": + fallthrough + case "force": + s.UseColors = true + + default: + s.UseColors = false + } +} + // Run executes terrascan in CLI mode -func Run(configFile, format string, scanStruct *ScanOptions) { +func (s *ScanCommand) Run() error { // temp dir to download the remote repo tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) defer os.RemoveAll(tempDir) // download remote repository - path, err := downloadRemoteRepository(scanStruct.RemoteType, scanStruct.RemoteURL, tempDir) + err := s.downloadRemoteRepository(tempDir) if err != nil { - return - } - - if path != "" { - scanStruct.IacDirPath = path + return err } // create a new runtime executor for processing IaC - executor, err := runtime.NewExecutor(scanStruct.IacType, scanStruct.IacVersion, scanStruct.PolicyType, - scanStruct.IacFilePath, scanStruct.IacDirPath, configFile, scanStruct.PolicyPath) + executor, err := runtime.NewExecutor(s.iacType, s.iacVersion, s.policyType, + s.iacFilePath, s.iacDirPath, s.configFile, s.policyPath) if err != nil { - return + return err } // executor output results, err := executor.Execute() if err != nil { - return + return err } // write results to console - err = writeResults(results, scanStruct.UseColors, scanStruct.Verbose, scanStruct.ConfigOnly, format) + err = s.writeResults(results) if err != nil { zap.S().Error("failed to write results", zap.Error(err)) - return + return err } if results.Violations.ViolationStore.Summary.ViolatedPolicies != 0 && flag.Lookup("test.v") == nil { os.RemoveAll(tempDir) os.Exit(3) } + return nil } -func downloadRemoteRepository(remoteType, remoteURL, tempDir string) (string, error) { +func (s *ScanCommand) downloadRemoteRepository(tempDir string) error { d := downloader.NewDownloader() - path, err := d.DownloadWithType(remoteType, remoteURL, tempDir) + path, err := d.DownloadWithType(s.remoteType, s.remoteURL, tempDir) + if path != "" { + s.iacDirPath = path + } if err == downloader.ErrEmptyURLType { // url and type empty, proceed with regular scanning zap.S().Debugf("remote url and type not configured, proceeding with regular scanning") } else if err != nil { // some error while downloading remote repository - return path, err + return err } - return path, nil + return nil } -func writeResults(results runtime.Output, useColors, verbose, configOnly bool, format string) error { +func (s ScanCommand) writeResults(results runtime.Output) error { // add verbose flag to the scan summary - results.Violations.ViolationStore.Summary.ShowViolationDetails = verbose + results.Violations.ViolationStore.Summary.ShowViolationDetails = s.Verbose - outputWriter := NewOutputWriter(useColors) + outputWriter := NewOutputWriter(s.UseColors) - if configOnly { - // human readable output doesn't support --config-only flag - // if --config-only flag is set, then exit with an error - // asking the user to use yaml or json output format - if strings.EqualFold(format, humanOutputFormat) { - return errors.New("please use yaml or json output format when using --config-only flag") - } - writer.Write(format, results.ResourceConfig, outputWriter) - } else { - writer.Write(format, results.Violations, outputWriter) + if s.configOnly { + return writer.Write(s.outputType, results.ResourceConfig, outputWriter) } - return nil + return writer.Write(s.outputType, results.Violations, outputWriter) } diff --git a/pkg/cli/run_test.go b/pkg/cli/run_test.go index 312a11dd5..49b7fbf9b 100644 --- a/pkg/cli/run_test.go +++ b/pkg/cli/run_test.go @@ -33,55 +33,55 @@ func TestRun(t *testing.T) { name string configFile string format string - scanOptions *ScanOptions + scanCommand *ScanCommand stdOut string want string wantErr error }{ { name: "normal terraform run", - scanOptions: &ScanOptions{ - PolicyType: []string{"terraform"}, - IacDirPath: "testdata/run-test", + scanCommand: &ScanCommand{ + policyType: []string{"terraform"}, + iacDirPath: "testdata/run-test", }, }, { name: "normal k8s run", - scanOptions: &ScanOptions{ - PolicyType: []string{"k8s"}, - IacDirPath: "testdata/run-test", + scanCommand: &ScanCommand{ + policyType: []string{"k8s"}, + iacDirPath: "testdata/run-test", }, }, { name: "config-only flag terraform", - scanOptions: &ScanOptions{ - PolicyType: []string{"terraform"}, - IacDirPath: "testdata/run-test/config-only.tf", - ConfigOnly: true, + scanCommand: &ScanCommand{ + policyType: []string{"terraform"}, + iacDirPath: "testdata/run-test/config-only.tf", + configOnly: true, }, }, { name: "config-only flag k8s", - scanOptions: &ScanOptions{ - PolicyType: []string{"k8s"}, - IacDirPath: "testdata/run-test/config-only.yaml", - ConfigOnly: true, + scanCommand: &ScanCommand{ + policyType: []string{"k8s"}, + iacDirPath: "testdata/run-test/config-only.yaml", + configOnly: true, }, }, { name: "config-only flag true with human readable format", - scanOptions: &ScanOptions{ - PolicyType: []string{"terraform"}, - IacDirPath: "testdata/run-test/config-only.tf", - ConfigOnly: true, + scanCommand: &ScanCommand{ + policyType: []string{"terraform"}, + iacDirPath: "testdata/run-test/config-only.tf", + configOnly: true, }, format: "human", }, { name: "config-only flag false with human readable format", - scanOptions: &ScanOptions{ - PolicyType: []string{"k8s"}, - IacDirPath: "testdata/run-test/config-only.yaml", + scanCommand: &ScanCommand{ + policyType: []string{"k8s"}, + iacDirPath: "testdata/run-test/config-only.yaml", }, format: "human", }, @@ -89,117 +89,258 @@ func TestRun(t *testing.T) { for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - Run(tt.configFile, tt.format, scanOptions) + tt.scanCommand.Run() + }) + } +} + +func TestScanCommand_downloadRemoteRepository(t *testing.T) { + testTempdir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) + defer os.RemoveAll(testTempdir) + + type fields struct { + RemoteType string + RemoteURL string + } + tests := []struct { + name string + fields fields + tempDir string + want string + wantErr bool + }{ + { + name: "blank input parameters", + fields: fields{ + RemoteType: "", + RemoteURL: "", + }, + tempDir: "", + }, + { + name: "invalid input parameters", + fields: fields{ + RemoteType: "test", + RemoteURL: "test", + }, + tempDir: "test", + wantErr: true, + }, + { + name: "invalid input parameters", + fields: fields{ + RemoteType: "git", + RemoteURL: "github.com/accurics/terrascan", + }, + tempDir: testTempdir, + want: testTempdir, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := ScanCommand{ + remoteType: tt.fields.RemoteType, + remoteURL: tt.fields.RemoteURL, + } + err := s.downloadRemoteRepository(tt.tempDir) + if (err != nil) != tt.wantErr { + t.Errorf("ScanOptions.downloadRemoteRepository() error = %v, wantErr %v", err, tt.wantErr) + return + } + if s.iacDirPath != tt.want { + t.Errorf("ScanOptions.downloadRemoteRepository() = %v, want %v", s.iacDirPath, tt.want) + } }) } } -func TestWriteResults(t *testing.T) { +func TestScanCommand_writeResults(t *testing.T) { testInput := runtime.Output{ ResourceConfig: output.AllResourceConfigs{}, Violations: policy.EngineOutput{ ViolationStore: &results.ViolationStore{}, }, } - type args struct { - results runtime.Output - useColors bool - verbose bool + + type fields struct { + ConfigOnly bool + OutputType string + } + tests := []struct { + name string + fields fields + args runtime.Output + wantErr bool + }{ + { + name: "config only true", + fields: fields{ + ConfigOnly: true, + OutputType: "yaml", + }, + args: testInput, + }, + { + name: "config only false", + fields: fields{ + ConfigOnly: false, + OutputType: "json", + }, + args: testInput, + }, + { + // until we support config only flag for xml, this test case is for expected failure + name: "config only true for xml", + fields: fields{ + ConfigOnly: true, + OutputType: "xml", + }, + args: testInput, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := ScanCommand{ + configOnly: tt.fields.ConfigOnly, + outputType: tt.fields.OutputType, + } + if err := s.writeResults(tt.args); (err != nil) != tt.wantErr { + t.Errorf("ScanOptions.writeResults() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestScanCommand_validate(t *testing.T) { + type fields struct { configOnly bool - format string + outputType string } tests := []struct { name string - args args + fields fields wantErr bool }{ { - name: "config only true with human readable output format", - args: args{ - results: testInput, + name: "validate --config-only with human readable output", + fields: fields{ configOnly: true, - format: "human", + outputType: "human", }, wantErr: true, }, { - name: "config only true with non human readable output format", - args: args{ - results: testInput, + name: "validate --config-only with non human readable output", + fields: fields{ configOnly: true, - format: "json", + outputType: "json", }, wantErr: false, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := ScanCommand{ + configOnly: tt.fields.configOnly, + outputType: tt.fields.outputType, + } + if err := s.validate(); (err != nil) != tt.wantErr { + t.Errorf("ScanCommand.validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestScanCommand_initColor(t *testing.T) { + type fields struct { + useColors string + } + tests := []struct { + name string + fields fields + want bool + }{ { - name: "config only false", - args: args{ - results: testInput, - configOnly: false, - format: "human", + name: "auto", + fields: fields{ + useColors: "auto", + }, + }, + { + name: "true", + fields: fields{ + useColors: "true", + }, + want: true, + }, + { + name: "1", + fields: fields{ + useColors: "1", + }, + want: true, + }, + { + name: "false", + fields: fields{ + useColors: "false", }, - wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := writeResults(tt.args.results, tt.args.useColors, tt.args.verbose, tt.args.configOnly, tt.args.format); (err != nil) != tt.wantErr { - t.Errorf("writeResults() error = gotErr: %v, wantErr: %v", err, tt.wantErr) + s := &ScanCommand{ + useColors: tt.fields.useColors, + } + s.initColor() + if s.useColors != "auto" { + if s.UseColors != tt.want { + t.Errorf("ScanCommand.initColor() incorrect value for UseColors, got: %v, want %v", s.useColors, tt.want) + } } }) } } -func TestDownloadRemoteRepository(t *testing.T) { - testTempdir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) - - type args struct { - remoteType string - remoteURL string - tempDir string +func TestScanCommand_Init(t *testing.T) { + type fields struct { + configOnly bool + outputType string + useColors string } tests := []struct { name string - args args - want string + fields fields wantErr bool }{ { - name: "blank input paramters", - args: args{ - remoteType: "", - remoteURL: "", - tempDir: "", - }, - }, - { - name: "invalid input parameters", - args: args{ - remoteType: "test", - remoteURL: "test", - tempDir: "test", + name: "test for init fail", + fields: fields{ + useColors: "auto", + outputType: "human", + configOnly: true, }, wantErr: true, }, { - name: "valid inputs paramters", - args: args{ - remoteType: "git", - remoteURL: "github.com/accurics/terrascan", - tempDir: testTempdir, + name: "test for init fail", + fields: fields{ + useColors: "auto", + outputType: "human", + configOnly: false, }, - want: testTempdir, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := downloadRemoteRepository(tt.args.remoteType, tt.args.remoteURL, tt.args.tempDir) - if (err != nil) != tt.wantErr { - t.Errorf("downloadRemoteRepository() error = %v, wantErr %v", err, tt.wantErr) - return + s := &ScanCommand{ + configOnly: tt.fields.configOnly, + outputType: tt.fields.outputType, + useColors: tt.fields.useColors, } - if got != tt.want { - t.Errorf("downloadRemoteRepository() = %v, want %v", got, tt.want) + if err := s.Init(); (err != nil) != tt.wantErr { + t.Errorf("ScanCommand.Init() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/pkg/cli/scan.go b/pkg/cli/scan.go index 0f5f9b9bb..7bbad75c4 100644 --- a/pkg/cli/scan.go +++ b/pkg/cli/scan.go @@ -18,55 +18,15 @@ package cli import ( "fmt" - "os" "strings" iacProvider "github.com/accurics/terrascan/pkg/iac-providers" "github.com/accurics/terrascan/pkg/policy" - "github.com/mattn/go-isatty" "github.com/spf13/cobra" "go.uber.org/zap" ) -// ScanOptions represents scan command and its optional flags -type ScanOptions struct { - // PolicyPath Policy path directory - PolicyPath []string - - // PolicyType Cloud type (aws, azure, gcp, github) - PolicyType []string - - // IacType IaC type (terraform) - IacType string - - // IacVersion IaC version (for terraform:v12) - IacVersion string - - // IacFilePath Path to a single IaC file - IacFilePath string - - // IacDirPath Path to a directory containing one or more IaC files - IacDirPath string - - // RemoteType indicates the type of remote backend. Supported backends are - // git s3, gcs, http. - RemoteType string - - // RemoteURL points to the remote Iac repository on git, s3, gcs, http - RemoteURL string - - // ConfigOnly will output resource config (should only be used for debugging purposes) - ConfigOnly bool - - // UseColors indicates whether to use color output - UseColors bool - useColors string // used for flag processing - - // Verbose indicates whether to display all fields in default human readlbe output - Verbose bool -} - -var scanOptions = &ScanOptions{} +var scanCommand = new(ScanCommand) var scanCmd = &cobra.Command{ Use: "scan", @@ -75,51 +35,29 @@ var scanCmd = &cobra.Command{ Detect compliance and security violations across Infrastructure as Code to mitigate risk before provisioning cloud native infrastructure. `, - PreRun: func(cmd *cobra.Command, args []string) { - switch strings.ToLower(scanOptions.useColors) { - case "auto": - if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { - scanOptions.UseColors = true - } else { - scanOptions.UseColors = false - } - - case "true": - fallthrough - case "t": - fallthrough - case "y": - fallthrough - case "1": - fallthrough - case "force": - scanOptions.UseColors = true - - default: - scanOptions.UseColors = false - } - initial(cmd, args) - }, - Run: scan, + PreRun: initial, + Run: scan, } func scan(cmd *cobra.Command, args []string) { zap.S().Debug("running terrascan in cli mode") - Run(ConfigFile, OutputType, scanOptions) + scanCommand.configFile = ConfigFile + scanCommand.outputType = OutputType + scanCommand.StartScan() } func init() { - scanCmd.Flags().StringSliceVarP(&scanOptions.PolicyType, "policy-type", "t", []string{"all"}, fmt.Sprintf("policy type (%s)", strings.Join(policy.SupportedPolicyTypes(true), ", "))) - scanCmd.Flags().StringVarP(&scanOptions.IacType, "iac-type", "i", "", fmt.Sprintf("iac type (%v)", strings.Join(iacProvider.SupportedIacProviders(), ", "))) - scanCmd.Flags().StringVarP(&scanOptions.IacVersion, "iac-version", "", "", fmt.Sprintf("iac version (%v)", strings.Join(iacProvider.SupportedIacVersions(), ", "))) - scanCmd.Flags().StringVarP(&scanOptions.IacFilePath, "iac-file", "f", "", "path to a single IaC file") - scanCmd.Flags().StringVarP(&scanOptions.IacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") - scanCmd.Flags().StringArrayVarP(&scanOptions.PolicyPath, "policy-path", "p", []string{}, "policy path directory") - scanCmd.Flags().StringVarP(&scanOptions.RemoteType, "remote-type", "r", "", "type of remote backend (git, s3, gcs, http)") - scanCmd.Flags().StringVarP(&scanOptions.RemoteURL, "remote-url", "u", "", "url pointing to remote IaC repository") - scanCmd.Flags().BoolVarP(&scanOptions.ConfigOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") + scanCmd.Flags().StringSliceVarP(&scanCommand.policyType, "policy-type", "t", []string{"all"}, fmt.Sprintf("policy type (%s)", strings.Join(policy.SupportedPolicyTypes(true), ", "))) + scanCmd.Flags().StringVarP(&scanCommand.iacType, "iac-type", "i", "", fmt.Sprintf("iac type (%v)", strings.Join(iacProvider.SupportedIacProviders(), ", "))) + scanCmd.Flags().StringVarP(&scanCommand.iacVersion, "iac-version", "", "", fmt.Sprintf("iac version (%v)", strings.Join(iacProvider.SupportedIacVersions(), ", "))) + scanCmd.Flags().StringVarP(&scanCommand.iacFilePath, "iac-file", "f", "", "path to a single IaC file") + scanCmd.Flags().StringVarP(&scanCommand.iacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") + scanCmd.Flags().StringArrayVarP(&scanCommand.policyPath, "policy-path", "p", []string{}, "policy path directory") + scanCmd.Flags().StringVarP(&scanCommand.remoteType, "remote-type", "r", "", "type of remote backend (git, s3, gcs, http)") + scanCmd.Flags().StringVarP(&scanCommand.remoteURL, "remote-url", "u", "", "url pointing to remote IaC repository") + scanCmd.Flags().BoolVarP(&scanCommand.configOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") // flag passes a string, but we normalize to bool in PreRun - scanCmd.Flags().StringVar(&scanOptions.useColors, "use-colors", "auto", "color output (auto, t, f)") - scanCmd.Flags().BoolVarP(&scanOptions.Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)") + scanCmd.Flags().StringVar(&scanCommand.useColors, "use-colors", "auto", "color output (auto, t, f)") + scanCmd.Flags().BoolVarP(&scanCommand.Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)") RegisterCommand(rootCmd, scanCmd) } From 10ee6d56225b1a2161041be9f1daabc89b751654 Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Mon, 21 Dec 2020 01:42:23 +0530 Subject: [PATCH 3/6] 1. modify existing tests of Run 2. add new tests for Run --- pkg/cli/run_test.go | 132 ++++++++++++++---- .../run-test/kustomize-test/configMap.yaml | 7 + .../run-test/kustomize-test/deployment.yaml | 33 +++++ .../kustomize-test/kustomization.yaml | 8 ++ 4 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 pkg/cli/testdata/run-test/kustomize-test/configMap.yaml create mode 100644 pkg/cli/testdata/run-test/kustomize-test/deployment.yaml create mode 100644 pkg/cli/testdata/run-test/kustomize-test/kustomization.yaml diff --git a/pkg/cli/run_test.go b/pkg/cli/run_test.go index 49b7fbf9b..1719b2d86 100644 --- a/pkg/cli/run_test.go +++ b/pkg/cli/run_test.go @@ -32,64 +32,94 @@ func TestRun(t *testing.T) { table := []struct { name string configFile string - format string scanCommand *ScanCommand stdOut string want string - wantErr error + wantErr bool }{ { name: "normal terraform run", scanCommand: &ScanCommand{ + // policy type terraform is not supported, error expected policyType: []string{"terraform"}, iacDirPath: "testdata/run-test", }, + wantErr: true, + }, + { + name: "normal terraform run with successful output", + scanCommand: &ScanCommand{ + policyType: []string{"all"}, + iacDirPath: "testdata/run-test", + outputType: "json", + }, }, { name: "normal k8s run", scanCommand: &ScanCommand{ policyType: []string{"k8s"}, + // kustomization.y(a)ml file not present under the dir path, error expected iacDirPath: "testdata/run-test", }, + wantErr: true, + }, + { + name: "normal k8s run with successful output", + scanCommand: &ScanCommand{ + policyType: []string{"k8s"}, + iacDirPath: "testdata/run-test/kustomize-test", + outputType: "human", + }, }, { name: "config-only flag terraform", scanCommand: &ScanCommand{ - policyType: []string{"terraform"}, - iacDirPath: "testdata/run-test/config-only.tf", - configOnly: true, + policyType: []string{"all"}, + iacFilePath: "testdata/run-test/config-only.tf", + configOnly: true, + outputType: "yaml", }, }, { name: "config-only flag k8s", scanCommand: &ScanCommand{ policyType: []string{"k8s"}, - iacDirPath: "testdata/run-test/config-only.yaml", + iacDirPath: "testdata/run-test/kustomize-test", configOnly: true, + outputType: "json", }, }, { - name: "config-only flag true with human readable format", + // xml doesn't support config-only, error expected + // modify the test results when xml supports config-only + name: "config-only flag true with xml output format", scanCommand: &ScanCommand{ - policyType: []string{"terraform"}, - iacDirPath: "testdata/run-test/config-only.tf", - configOnly: true, + policyType: []string{"all"}, + iacFilePath: "testdata/run-test/config-only.tf", + configOnly: true, + outputType: "xml", }, - format: "human", + wantErr: true, }, { - name: "config-only flag false with human readable format", + name: "fail to download remote repository", scanCommand: &ScanCommand{ - policyType: []string{"k8s"}, - iacDirPath: "testdata/run-test/config-only.yaml", + policyType: []string{"all"}, + iacFilePath: "testdata/run-test/config-only.tf", + remoteURL: "test", + remoteType: "test", }, - format: "human", + wantErr: true, }, } for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - tt.scanCommand.Run() + err := tt.scanCommand.Run() + if (err != nil) != tt.wantErr { + t.Errorf("ScanCommand.Run() error = %v, wantErr %v", err, tt.wantErr) + return + } }) } } @@ -127,7 +157,7 @@ func TestScanCommand_downloadRemoteRepository(t *testing.T) { wantErr: true, }, { - name: "invalid input parameters", + name: "valid input parameters", fields: fields{ RemoteType: "git", RemoteURL: "github.com/accurics/terrascan", @@ -144,11 +174,11 @@ func TestScanCommand_downloadRemoteRepository(t *testing.T) { } err := s.downloadRemoteRepository(tt.tempDir) if (err != nil) != tt.wantErr { - t.Errorf("ScanOptions.downloadRemoteRepository() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanCommand.downloadRemoteRepository() error = %v, wantErr %v", err, tt.wantErr) return } if s.iacDirPath != tt.want { - t.Errorf("ScanOptions.downloadRemoteRepository() = %v, want %v", s.iacDirPath, tt.want) + t.Errorf("ScanCommand.downloadRemoteRepository() = %v, want %v", s.iacDirPath, tt.want) } }) } @@ -206,7 +236,7 @@ func TestScanCommand_writeResults(t *testing.T) { outputType: tt.fields.OutputType, } if err := s.writeResults(tt.args); (err != nil) != tt.wantErr { - t.Errorf("ScanOptions.writeResults() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanCommand.writeResults() error = %v, wantErr %v", err, tt.wantErr) } }) } @@ -262,27 +292,27 @@ func TestScanCommand_initColor(t *testing.T) { want bool }{ { - name: "auto", + name: "test for auto as input", fields: fields{ useColors: "auto", }, }, { - name: "true", + name: "test for true as input", fields: fields{ useColors: "true", }, want: true, }, { - name: "1", + name: "test for 1 as input", fields: fields{ useColors: "1", }, want: true, }, { - name: "false", + name: "test for false as input", fields: fields{ useColors: "false", }, @@ -324,7 +354,7 @@ func TestScanCommand_Init(t *testing.T) { wantErr: true, }, { - name: "test for init fail", + name: "test for init success", fields: fields{ useColors: "auto", outputType: "human", @@ -345,3 +375,55 @@ func TestScanCommand_Init(t *testing.T) { }) } } + +func TestScanCommand_StartScan(t *testing.T) { + type fields struct { + policyType []string + iacDirPath string + configOnly bool + outputType string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "failure in init", + fields: fields{ + configOnly: true, + outputType: "human", + }, + wantErr: true, + }, + { + name: "failure in run", + fields: fields{ + policyType: []string{"terraform"}, + iacDirPath: "testdata/run-test", + }, + wantErr: true, + }, + { + name: "successful scan", + fields: fields{ + policyType: []string{"all"}, + iacDirPath: "testdata/run-test", + outputType: "json", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &ScanCommand{ + policyType: tt.fields.policyType, + iacDirPath: tt.fields.iacDirPath, + configOnly: tt.fields.configOnly, + outputType: tt.fields.outputType, + } + if err := s.StartScan(); (err != nil) != tt.wantErr { + t.Errorf("ScanCommand.StartScan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/cli/testdata/run-test/kustomize-test/configMap.yaml b/pkg/cli/testdata/run-test/kustomize-test/configMap.yaml new file mode 100644 index 000000000..e335ab8cc --- /dev/null +++ b/pkg/cli/testdata/run-test/kustomize-test/configMap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/pkg/cli/testdata/run-test/kustomize-test/deployment.yaml b/pkg/cli/testdata/run-test/kustomize-test/deployment.yaml new file mode 100644 index 000000000..00e5eb937 --- /dev/null +++ b/pkg/cli/testdata/run-test/kustomize-test/deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + selector: + matchLabels: + deployment: hello + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/pkg/cli/testdata/run-test/kustomize-test/kustomization.yaml b/pkg/cli/testdata/run-test/kustomize-test/kustomization.yaml new file mode 100644 index 000000000..882d52bfe --- /dev/null +++ b/pkg/cli/testdata/run-test/kustomize-test/kustomization.yaml @@ -0,0 +1,8 @@ +# Example configuration for the webserver +# at https://github.com/monopole/hello +commonLabels: + app: hello + +resources: +- deployment.yaml +- configMap.yaml From 9d40d8e8124d25361d1313a999b935dd141321ee Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Mon, 21 Dec 2020 10:44:24 +0530 Subject: [PATCH 4/6] 1. added setup func to download policies 2. fix code smells --- pkg/cli/run_test.go | 50 ++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/pkg/cli/run_test.go b/pkg/cli/run_test.go index 1719b2d86..aad9649d7 100644 --- a/pkg/cli/run_test.go +++ b/pkg/cli/run_test.go @@ -28,7 +28,29 @@ import ( "github.com/accurics/terrascan/pkg/utils" ) +func TestMain(m *testing.M) { + setup() + code := m.Run() + shutdown() + os.Exit(code) +} + +func setup() { + // to download the policies for Run test + // downloads the policies at $HOME/.terrascan + initial(nil, nil) +} + +func shutdown() { + // remove the downloaded policies + os.RemoveAll(os.Getenv("HOME") + "/.terrascan") +} + func TestRun(t *testing.T) { + testDirPath := "testdata/run-test" + kustomizeTestDirPath := testDirPath + "/kustomize-test" + testTerraformFilePath := testDirPath + "/config-only.tf" + table := []struct { name string configFile string @@ -42,7 +64,7 @@ func TestRun(t *testing.T) { scanCommand: &ScanCommand{ // policy type terraform is not supported, error expected policyType: []string{"terraform"}, - iacDirPath: "testdata/run-test", + iacDirPath: testDirPath, }, wantErr: true, }, @@ -50,7 +72,7 @@ func TestRun(t *testing.T) { name: "normal terraform run with successful output", scanCommand: &ScanCommand{ policyType: []string{"all"}, - iacDirPath: "testdata/run-test", + iacDirPath: testDirPath, outputType: "json", }, }, @@ -59,7 +81,7 @@ func TestRun(t *testing.T) { scanCommand: &ScanCommand{ policyType: []string{"k8s"}, // kustomization.y(a)ml file not present under the dir path, error expected - iacDirPath: "testdata/run-test", + iacDirPath: testDirPath, }, wantErr: true, }, @@ -67,7 +89,7 @@ func TestRun(t *testing.T) { name: "normal k8s run with successful output", scanCommand: &ScanCommand{ policyType: []string{"k8s"}, - iacDirPath: "testdata/run-test/kustomize-test", + iacDirPath: kustomizeTestDirPath, outputType: "human", }, }, @@ -75,7 +97,7 @@ func TestRun(t *testing.T) { name: "config-only flag terraform", scanCommand: &ScanCommand{ policyType: []string{"all"}, - iacFilePath: "testdata/run-test/config-only.tf", + iacFilePath: testTerraformFilePath, configOnly: true, outputType: "yaml", }, @@ -84,7 +106,7 @@ func TestRun(t *testing.T) { name: "config-only flag k8s", scanCommand: &ScanCommand{ policyType: []string{"k8s"}, - iacDirPath: "testdata/run-test/kustomize-test", + iacDirPath: kustomizeTestDirPath, configOnly: true, outputType: "json", }, @@ -95,7 +117,7 @@ func TestRun(t *testing.T) { name: "config-only flag true with xml output format", scanCommand: &ScanCommand{ policyType: []string{"all"}, - iacFilePath: "testdata/run-test/config-only.tf", + iacFilePath: testTerraformFilePath, configOnly: true, outputType: "xml", }, @@ -105,7 +127,7 @@ func TestRun(t *testing.T) { name: "fail to download remote repository", scanCommand: &ScanCommand{ policyType: []string{"all"}, - iacFilePath: "testdata/run-test/config-only.tf", + iacFilePath: testTerraformFilePath, remoteURL: "test", remoteType: "test", }, @@ -124,7 +146,7 @@ func TestRun(t *testing.T) { } } -func TestScanCommand_downloadRemoteRepository(t *testing.T) { +func TestScanCommandDownloadRemoteRepository(t *testing.T) { testTempdir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) defer os.RemoveAll(testTempdir) @@ -184,7 +206,7 @@ func TestScanCommand_downloadRemoteRepository(t *testing.T) { } } -func TestScanCommand_writeResults(t *testing.T) { +func TestScanCommandWriteResults(t *testing.T) { testInput := runtime.Output{ ResourceConfig: output.AllResourceConfigs{}, Violations: policy.EngineOutput{ @@ -242,7 +264,7 @@ func TestScanCommand_writeResults(t *testing.T) { } } -func TestScanCommand_validate(t *testing.T) { +func TestScanCommandValidate(t *testing.T) { type fields struct { configOnly bool outputType string @@ -282,7 +304,7 @@ func TestScanCommand_validate(t *testing.T) { } } -func TestScanCommand_initColor(t *testing.T) { +func TestScanCommandInitColor(t *testing.T) { type fields struct { useColors string } @@ -333,7 +355,7 @@ func TestScanCommand_initColor(t *testing.T) { } } -func TestScanCommand_Init(t *testing.T) { +func TestScanCommandInit(t *testing.T) { type fields struct { configOnly bool outputType string @@ -376,7 +398,7 @@ func TestScanCommand_Init(t *testing.T) { } } -func TestScanCommand_StartScan(t *testing.T) { +func TestScanCommandStartScan(t *testing.T) { type fields struct { policyType []string iacDirPath string From 71645d2fa4c5648dbe48cf382b4e56db556336af Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Mon, 21 Dec 2020 11:34:15 +0530 Subject: [PATCH 5/6] rename ScanCommand to ScanOptions --- pkg/cli/run.go | 30 +++++++++++----------- pkg/cli/run_test.go | 62 ++++++++++++++++++++++----------------------- pkg/cli/scan.go | 30 +++++++++++----------- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/pkg/cli/run.go b/pkg/cli/run.go index 2ef0af854..05b7ed3d7 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -35,8 +35,8 @@ const ( humanOutputFormat = "human" ) -// ScanCommand represents scan command and its optional flags -type ScanCommand struct { +// ScanOptions represents scan command and its optional flags +type ScanOptions struct { // Policy path directory policyPath []string @@ -79,22 +79,22 @@ type ScanCommand struct { Verbose bool } -// StartScan starts the terrascan scan command -func (s *ScanCommand) StartScan() error { - err := s.Init() - if err != nil { +// Scan executes the terrascan scan command +func (s *ScanOptions) Scan() error { + if err := s.Init(); err != nil { + zap.S().Error("scan init failed", zap.Error(err)) return err } - err = s.Run() - if err != nil { + if err := s.Run(); err != nil { + zap.S().Error("scan run failed", zap.Error(err)) return err } return nil } -//Init initalises and validates ScanCommand -func (s *ScanCommand) Init() error { +//Init initalises and validates ScanOptions +func (s *ScanOptions) Init() error { s.initColor() err := s.validate() if err != nil { @@ -106,7 +106,7 @@ func (s *ScanCommand) Init() error { // validate config only for human readable output // rest command options are validated by the executor -func (s ScanCommand) validate() error { +func (s ScanOptions) validate() error { // human readable output doesn't support --config-only flag // if --config-only flag is set, then exit with an error // asking the user to use yaml or json output format @@ -117,7 +117,7 @@ func (s ScanCommand) validate() error { } // initialises use colors options -func (s *ScanCommand) initColor() { +func (s *ScanOptions) initColor() { switch strings.ToLower(s.useColors) { case "auto": if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { @@ -143,7 +143,7 @@ func (s *ScanCommand) initColor() { } // Run executes terrascan in CLI mode -func (s *ScanCommand) Run() error { +func (s *ScanOptions) Run() error { // temp dir to download the remote repo tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) @@ -182,7 +182,7 @@ func (s *ScanCommand) Run() error { return nil } -func (s *ScanCommand) downloadRemoteRepository(tempDir string) error { +func (s *ScanOptions) downloadRemoteRepository(tempDir string) error { d := downloader.NewDownloader() path, err := d.DownloadWithType(s.remoteType, s.remoteURL, tempDir) if path != "" { @@ -198,7 +198,7 @@ func (s *ScanCommand) downloadRemoteRepository(tempDir string) error { return nil } -func (s ScanCommand) writeResults(results runtime.Output) error { +func (s ScanOptions) writeResults(results runtime.Output) error { // add verbose flag to the scan summary results.Violations.ViolationStore.Summary.ShowViolationDetails = s.Verbose diff --git a/pkg/cli/run_test.go b/pkg/cli/run_test.go index aad9649d7..7b3574286 100644 --- a/pkg/cli/run_test.go +++ b/pkg/cli/run_test.go @@ -54,14 +54,14 @@ func TestRun(t *testing.T) { table := []struct { name string configFile string - scanCommand *ScanCommand + scanOptions *ScanOptions stdOut string want string wantErr bool }{ { name: "normal terraform run", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ // policy type terraform is not supported, error expected policyType: []string{"terraform"}, iacDirPath: testDirPath, @@ -70,7 +70,7 @@ func TestRun(t *testing.T) { }, { name: "normal terraform run with successful output", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"all"}, iacDirPath: testDirPath, outputType: "json", @@ -78,7 +78,7 @@ func TestRun(t *testing.T) { }, { name: "normal k8s run", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"k8s"}, // kustomization.y(a)ml file not present under the dir path, error expected iacDirPath: testDirPath, @@ -87,7 +87,7 @@ func TestRun(t *testing.T) { }, { name: "normal k8s run with successful output", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"k8s"}, iacDirPath: kustomizeTestDirPath, outputType: "human", @@ -95,7 +95,7 @@ func TestRun(t *testing.T) { }, { name: "config-only flag terraform", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"all"}, iacFilePath: testTerraformFilePath, configOnly: true, @@ -104,7 +104,7 @@ func TestRun(t *testing.T) { }, { name: "config-only flag k8s", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"k8s"}, iacDirPath: kustomizeTestDirPath, configOnly: true, @@ -115,7 +115,7 @@ func TestRun(t *testing.T) { // xml doesn't support config-only, error expected // modify the test results when xml supports config-only name: "config-only flag true with xml output format", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"all"}, iacFilePath: testTerraformFilePath, configOnly: true, @@ -125,7 +125,7 @@ func TestRun(t *testing.T) { }, { name: "fail to download remote repository", - scanCommand: &ScanCommand{ + scanOptions: &ScanOptions{ policyType: []string{"all"}, iacFilePath: testTerraformFilePath, remoteURL: "test", @@ -137,16 +137,16 @@ func TestRun(t *testing.T) { for _, tt := range table { t.Run(tt.name, func(t *testing.T) { - err := tt.scanCommand.Run() + err := tt.scanOptions.Run() if (err != nil) != tt.wantErr { - t.Errorf("ScanCommand.Run() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanOptions.Run() error = %v, wantErr %v", err, tt.wantErr) return } }) } } -func TestScanCommandDownloadRemoteRepository(t *testing.T) { +func TestScanOptionsDownloadRemoteRepository(t *testing.T) { testTempdir := filepath.Join(os.TempDir(), utils.GenRandomString(6)) defer os.RemoveAll(testTempdir) @@ -190,23 +190,23 @@ func TestScanCommandDownloadRemoteRepository(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := ScanCommand{ + s := ScanOptions{ remoteType: tt.fields.RemoteType, remoteURL: tt.fields.RemoteURL, } err := s.downloadRemoteRepository(tt.tempDir) if (err != nil) != tt.wantErr { - t.Errorf("ScanCommand.downloadRemoteRepository() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanOptions.downloadRemoteRepository() error = %v, wantErr %v", err, tt.wantErr) return } if s.iacDirPath != tt.want { - t.Errorf("ScanCommand.downloadRemoteRepository() = %v, want %v", s.iacDirPath, tt.want) + t.Errorf("ScanOptions.downloadRemoteRepository() = %v, want %v", s.iacDirPath, tt.want) } }) } } -func TestScanCommandWriteResults(t *testing.T) { +func TestScanOptionsWriteResults(t *testing.T) { testInput := runtime.Output{ ResourceConfig: output.AllResourceConfigs{}, Violations: policy.EngineOutput{ @@ -253,18 +253,18 @@ func TestScanCommandWriteResults(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := ScanCommand{ + s := ScanOptions{ configOnly: tt.fields.ConfigOnly, outputType: tt.fields.OutputType, } if err := s.writeResults(tt.args); (err != nil) != tt.wantErr { - t.Errorf("ScanCommand.writeResults() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanOptions.writeResults() error = %v, wantErr %v", err, tt.wantErr) } }) } } -func TestScanCommandValidate(t *testing.T) { +func TestScanOptionsValidate(t *testing.T) { type fields struct { configOnly bool outputType string @@ -293,18 +293,18 @@ func TestScanCommandValidate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := ScanCommand{ + s := ScanOptions{ configOnly: tt.fields.configOnly, outputType: tt.fields.outputType, } if err := s.validate(); (err != nil) != tt.wantErr { - t.Errorf("ScanCommand.validate() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanOptions.validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } -func TestScanCommandInitColor(t *testing.T) { +func TestScanOptionsInitColor(t *testing.T) { type fields struct { useColors string } @@ -342,20 +342,20 @@ func TestScanCommandInitColor(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &ScanCommand{ + s := &ScanOptions{ useColors: tt.fields.useColors, } s.initColor() if s.useColors != "auto" { if s.UseColors != tt.want { - t.Errorf("ScanCommand.initColor() incorrect value for UseColors, got: %v, want %v", s.useColors, tt.want) + t.Errorf("ScanOptions.initColor() incorrect value for UseColors, got: %v, want %v", s.useColors, tt.want) } } }) } } -func TestScanCommandInit(t *testing.T) { +func TestScanOptionsInit(t *testing.T) { type fields struct { configOnly bool outputType string @@ -386,19 +386,19 @@ func TestScanCommandInit(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &ScanCommand{ + s := &ScanOptions{ configOnly: tt.fields.configOnly, outputType: tt.fields.outputType, useColors: tt.fields.useColors, } if err := s.Init(); (err != nil) != tt.wantErr { - t.Errorf("ScanCommand.Init() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ScanOptions.Init() error = %v, wantErr %v", err, tt.wantErr) } }) } } -func TestScanCommandStartScan(t *testing.T) { +func TestScanOptionsScan(t *testing.T) { type fields struct { policyType []string iacDirPath string @@ -437,14 +437,14 @@ func TestScanCommandStartScan(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &ScanCommand{ + s := &ScanOptions{ policyType: tt.fields.policyType, iacDirPath: tt.fields.iacDirPath, configOnly: tt.fields.configOnly, outputType: tt.fields.outputType, } - if err := s.StartScan(); (err != nil) != tt.wantErr { - t.Errorf("ScanCommand.StartScan() error = %v, wantErr %v", err, tt.wantErr) + if err := s.Scan(); (err != nil) != tt.wantErr { + t.Errorf("ScanOptions.Scan() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/pkg/cli/scan.go b/pkg/cli/scan.go index 7bbad75c4..511baaec8 100644 --- a/pkg/cli/scan.go +++ b/pkg/cli/scan.go @@ -26,7 +26,7 @@ import ( "go.uber.org/zap" ) -var scanCommand = new(ScanCommand) +var scanOptions = new(ScanOptions) var scanCmd = &cobra.Command{ Use: "scan", @@ -41,23 +41,23 @@ Detect compliance and security violations across Infrastructure as Code to mitig func scan(cmd *cobra.Command, args []string) { zap.S().Debug("running terrascan in cli mode") - scanCommand.configFile = ConfigFile - scanCommand.outputType = OutputType - scanCommand.StartScan() + scanOptions.configFile = ConfigFile + scanOptions.outputType = OutputType + scanOptions.Scan() } func init() { - scanCmd.Flags().StringSliceVarP(&scanCommand.policyType, "policy-type", "t", []string{"all"}, fmt.Sprintf("policy type (%s)", strings.Join(policy.SupportedPolicyTypes(true), ", "))) - scanCmd.Flags().StringVarP(&scanCommand.iacType, "iac-type", "i", "", fmt.Sprintf("iac type (%v)", strings.Join(iacProvider.SupportedIacProviders(), ", "))) - scanCmd.Flags().StringVarP(&scanCommand.iacVersion, "iac-version", "", "", fmt.Sprintf("iac version (%v)", strings.Join(iacProvider.SupportedIacVersions(), ", "))) - scanCmd.Flags().StringVarP(&scanCommand.iacFilePath, "iac-file", "f", "", "path to a single IaC file") - scanCmd.Flags().StringVarP(&scanCommand.iacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") - scanCmd.Flags().StringArrayVarP(&scanCommand.policyPath, "policy-path", "p", []string{}, "policy path directory") - scanCmd.Flags().StringVarP(&scanCommand.remoteType, "remote-type", "r", "", "type of remote backend (git, s3, gcs, http)") - scanCmd.Flags().StringVarP(&scanCommand.remoteURL, "remote-url", "u", "", "url pointing to remote IaC repository") - scanCmd.Flags().BoolVarP(&scanCommand.configOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") + scanCmd.Flags().StringSliceVarP(&scanOptions.policyType, "policy-type", "t", []string{"all"}, fmt.Sprintf("policy type (%s)", strings.Join(policy.SupportedPolicyTypes(true), ", "))) + scanCmd.Flags().StringVarP(&scanOptions.iacType, "iac-type", "i", "", fmt.Sprintf("iac type (%v)", strings.Join(iacProvider.SupportedIacProviders(), ", "))) + scanCmd.Flags().StringVarP(&scanOptions.iacVersion, "iac-version", "", "", fmt.Sprintf("iac version (%v)", strings.Join(iacProvider.SupportedIacVersions(), ", "))) + scanCmd.Flags().StringVarP(&scanOptions.iacFilePath, "iac-file", "f", "", "path to a single IaC file") + scanCmd.Flags().StringVarP(&scanOptions.iacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") + scanCmd.Flags().StringArrayVarP(&scanOptions.policyPath, "policy-path", "p", []string{}, "policy path directory") + scanCmd.Flags().StringVarP(&scanOptions.remoteType, "remote-type", "r", "", "type of remote backend (git, s3, gcs, http)") + scanCmd.Flags().StringVarP(&scanOptions.remoteURL, "remote-url", "u", "", "url pointing to remote IaC repository") + scanCmd.Flags().BoolVarP(&scanOptions.configOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") // flag passes a string, but we normalize to bool in PreRun - scanCmd.Flags().StringVar(&scanCommand.useColors, "use-colors", "auto", "color output (auto, t, f)") - scanCmd.Flags().BoolVarP(&scanCommand.Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)") + scanCmd.Flags().StringVar(&scanOptions.useColors, "use-colors", "auto", "color output (auto, t, f)") + scanCmd.Flags().BoolVarP(&scanOptions.Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)") RegisterCommand(rootCmd, scanCmd) } From e90045cda17a1cc457ef6827e75a5eb63c9b523e Mon Sep 17 00:00:00 2001 From: Pankaj Patil Date: Tue, 22 Dec 2020 11:03:23 +0530 Subject: [PATCH 6/6] introduce constructor func for ScanOptions --- pkg/cli/run.go | 5 +++++ pkg/cli/scan.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/cli/run.go b/pkg/cli/run.go index 05b7ed3d7..9bac751b2 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -79,6 +79,11 @@ type ScanOptions struct { Verbose bool } +// NewScanOptions returns a new pointer to ScanOptions +func NewScanOptions() *ScanOptions { + return new(ScanOptions) +} + // Scan executes the terrascan scan command func (s *ScanOptions) Scan() error { if err := s.Init(); err != nil { diff --git a/pkg/cli/scan.go b/pkg/cli/scan.go index 511baaec8..4ff3d0586 100644 --- a/pkg/cli/scan.go +++ b/pkg/cli/scan.go @@ -26,7 +26,7 @@ import ( "go.uber.org/zap" ) -var scanOptions = new(ScanOptions) +var scanOptions = NewScanOptions() var scanCmd = &cobra.Command{ Use: "scan",