Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bash output improvements #431

Merged
merged 17 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/cli/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func setDefaultCommandIfNonePresent() {
func Execute() {
rootCmd.PersistentFlags().StringVarP(&LogLevel, "log-level", "l", "info", "log level (debug, info, warn, error, panic, fatal)")
rootCmd.PersistentFlags().StringVarP(&LogType, "log-type", "x", "console", "log output type (console, json)")
rootCmd.PersistentFlags().StringVarP(&OutputType, "output", "o", "yaml", "output type (json, yaml, xml)")
rootCmd.PersistentFlags().StringVarP(&OutputType, "output", "o", "human", "output type (human, json, yaml, xml)")
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved
rootCmd.PersistentFlags().StringVarP(&ConfigFile, "config-path", "c", "", "config file path")

// Function to execute before processing commands
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var (
LogLevel string
// LogType Logging output type (console, json)
LogType string
// OutputType Violation output type (text, json, yaml, xml)
// OutputType Violation output type (human, json, yaml, xml)
OutputType string
// ConfigFile Config file path
ConfigFile string
Expand Down
64 changes: 49 additions & 15 deletions pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package cli

import (
"errors"
"flag"
"os"
"path/filepath"
"strings"

"github.com/accurics/terrascan/pkg/downloader"
"github.com/accurics/terrascan/pkg/runtime"
Expand All @@ -28,26 +30,26 @@ import (
"go.uber.org/zap"
)

const (
humanOutputFormat = "human"
)

// 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 bool) {
format, remoteType, remoteURL string, configOnly, useColors, verbose bool) {
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved

// temp dir to download the remote repo
tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6))
defer os.RemoveAll(tempDir)

// download remote repository
d := downloader.NewDownloader()
path, err := d.DownloadWithType(remoteType, remoteURL, tempDir)
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
path, err := downloadRemoteRepository(remoteType, remoteURL, tempDir)
if err != nil {
return
} else {
// successfully downloaded remote repository
}

if path != "" {
iacDirPath = path
}

Expand All @@ -64,16 +66,48 @@ func Run(iacType, iacVersion string, cloudType []string,
return
}

// write results to console
err = writeResults(results, useColors, verbose, configOnly, format)
if err != nil {
zap.S().Error("failed to write results", zap.Error(err))
return
}

if results.Violations.ViolationStore.Summary.ViolatedPolicies != 0 && flag.Lookup("test.v") == nil {
os.RemoveAll(tempDir)
os.Exit(3)
}
}

func downloadRemoteRepository(remoteType, remoteURL, tempDir string) (string, error) {
d := downloader.NewDownloader()
path, err := d.DownloadWithType(remoteType, remoteURL, tempDir)
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 path, nil
}

func writeResults(results runtime.Output, useColors, verbose, configOnly bool, format string) error {
// add verbose flag to the scan summary
results.Violations.ViolationStore.Summary.ShowViolationDetails = verbose

outputWriter := NewOutputWriter(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 results.Violations.ViolationStore.Count.TotalCount != 0 && flag.Lookup("test.v") == nil {
os.RemoveAll(tempDir)
os.Exit(3)
}
return nil
}
136 changes: 135 additions & 1 deletion pkg/cli/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@
package cli

import (
"os"
"path/filepath"
"testing"

"github.com/accurics/terrascan/pkg/iac-providers/output"
"github.com/accurics/terrascan/pkg/policy"
"github.com/accurics/terrascan/pkg/results"
"github.com/accurics/terrascan/pkg/runtime"
"github.com/accurics/terrascan/pkg/utils"
)

func TestRun(t *testing.T) {
Expand All @@ -26,10 +34,12 @@ func TestRun(t *testing.T) {
iacType string
iacVersion string
cloudType []string
format string
iacFilePath string
iacDirPath string
configFile string
configOnly bool
verbose bool
stdOut string
want string
wantErr error
Expand All @@ -56,11 +66,135 @@ func TestRun(t *testing.T) {
iacFilePath: "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 false with human readable format",
cloudType: []string{"k8s"},
iacFilePath: "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.configOnly, false)
Run(tt.iacType, tt.iacVersion, tt.cloudType, tt.iacFilePath, tt.iacDirPath, tt.configFile, []string{}, tt.format, "", "", tt.configOnly, false, tt.verbose)
})
}
}

func TestWriteResults(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
configOnly bool
format string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "config only true with human readable output format",
args: args{
results: testInput,
configOnly: true,
format: "human",
},
wantErr: true,
},
{
name: "config only true with non human readable output format",
args: args{
results: testInput,
configOnly: true,
format: "json",
},
wantErr: false,
},
{
name: "config only false",
args: args{
results: testInput,
configOnly: false,
format: "human",
},
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)
}
})
}
}

func TestDownloadRemoteRepository(t *testing.T) {
testTempdir := filepath.Join(os.TempDir(), utils.GenRandomString(6))

type args struct {
remoteType string
remoteURL string
tempDir string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "blank input paramters",
args: args{
remoteType: "",
remoteURL: "",
tempDir: "",
},
},
{
name: "invalid input parameters",
args: args{
remoteType: "test",
remoteURL: "test",
tempDir: "test",
},
wantErr: true,
},
{
name: "valid inputs paramters",
args: args{
remoteType: "git",
remoteURL: "github.com/accurics/terrascan",
tempDir: testTempdir,
},
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
}
if got != tt.want {
t.Errorf("downloadRemoteRepository() = %v, want %v", got, tt.want)
}
})
}
}
6 changes: 5 additions & 1 deletion pkg/cli/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ var (
// 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
williepaul marked this conversation as resolved.
Show resolved Hide resolved
)

var scanCmd = &cobra.Command{
Expand Down Expand Up @@ -100,7 +103,7 @@ 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)
PolicyPath, OutputType, RemoteType, RemoteURL, ConfigOnly, UseColors, Verbose)
}

func init() {
Expand All @@ -115,5 +118,6 @@ func init() {
scanCmd.Flags().BoolVarP(&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)")
RegisterCommand(rootCmd, scanCmd)
}
11 changes: 7 additions & 4 deletions pkg/policy/opa/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,16 +303,16 @@ func (e *Engine) reportViolation(regoData *RegoData, resource *output.ResourceCo

severity := regoData.Metadata.Severity
if strings.ToLower(severity) == "high" {
e.results.ViolationStore.Count.HighCount++
e.results.ViolationStore.Summary.HighCount++
} else if strings.ToLower(severity) == "medium" {
e.results.ViolationStore.Count.MediumCount++
e.results.ViolationStore.Summary.MediumCount++
} else if strings.ToLower(severity) == "low" {
e.results.ViolationStore.Count.LowCount++
e.results.ViolationStore.Summary.LowCount++
} else {
zap.S().Warn("invalid severity found in rule definition",
zap.String("rule id", violation.RuleID), zap.String("severity", severity))
}
e.results.ViolationStore.Count.TotalCount++
e.results.ViolationStore.Summary.ViolatedPolicies++

e.results.ViolationStore.AddResult(&violation)
}
Expand Down Expand Up @@ -390,5 +390,8 @@ func (e *Engine) Evaluate(engineInput policy.EngineInput) (policy.EngineOutput,
}

e.stats.runTime = time.Since(start)

// add the rule count of the policy engine to result summary
e.results.ViolationStore.Summary.TotalPolicies += e.stats.ruleCount
return e.results, nil
}
2 changes: 1 addition & 1 deletion pkg/policy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ func (me EngineOutput) AsViolationStore() results.ViolationStore {
}
return results.ViolationStore{
Violations: me.Violations,
Count: me.Count,
Summary: me.Summary,
}
}
Loading