Skip to content

Commit

Permalink
Merge pull request #436 from patilpankaj212/scan-refactor
Browse files Browse the repository at this point in the history
scan command refactor
  • Loading branch information
kanchwala-yusuf authored Dec 22, 2020
2 parents ce61b02 + e90045c commit c57bb06
Show file tree
Hide file tree
Showing 6 changed files with 538 additions and 196 deletions.
169 changes: 136 additions & 33 deletions pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,87 +27,190 @@ 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"
)

const (
humanOutputFormat = "human"
)

// ScanOptions represents scan command and its optional flags
type ScanOptions 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
}

// 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 {
zap.S().Error("scan init failed", zap.Error(err))
return err
}

if err := s.Run(); err != nil {
zap.S().Error("scan run failed", zap.Error(err))
return err
}
return nil
}

//Init initalises and validates ScanOptions
func (s *ScanOptions) 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 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
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 *ScanOptions) 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(iacType, iacVersion string, cloudType []string,
iacFilePath, iacDirPath, configFile string, policyPath []string,
format, remoteType, remoteURL string, configOnly, useColors, verbose bool) {
func (s *ScanOptions) 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(remoteType, remoteURL, tempDir)
err := s.downloadRemoteRepository(tempDir)
if err != nil {
return
}

if path != "" {
iacDirPath = path
return err
}

// create a new runtime executor for processing IaC
executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType,
iacFilePath, iacDirPath, configFile, 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, useColors, verbose, 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 *ScanOptions) 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 ScanOptions) 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)
}
Loading

0 comments on commit c57bb06

Please sign in to comment.