Skip to content

Commit

Permalink
incorporate review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
patilpankaj212 committed Apr 30, 2021
1 parent b5c0465 commit c8bf725
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 84 deletions.
128 changes: 62 additions & 66 deletions pkg/runtime/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package runtime

import (
"reflect"
"sort"

"go.uber.org/zap"
Expand All @@ -27,6 +26,7 @@ import (
"github.com/accurics/terrascan/pkg/notifications"
"github.com/accurics/terrascan/pkg/policy"
opa "github.com/accurics/terrascan/pkg/policy/opa"
"github.com/accurics/terrascan/pkg/utils"
"github.com/hashicorp/go-multierror"
)

Expand Down Expand Up @@ -160,38 +160,13 @@ func (e *Executor) Init() error {
func (e *Executor) Execute() (results Output, err error) {

var merr *multierror.Error
resourceConfig := make(output.AllResourceConfigs)
var resourceConfig output.AllResourceConfigs

// when dir path has value, only then it will 'all iac' scan
// when file path has value, we will go with the only iac provider in the list
if e.dirPath != "" {

// channel for directory scan response
scanRespChan := make(chan dirScanResp)

// create results output from Iac provider[s]
for _, iacP := range e.iacProviders {
go func(ip iacProvider.IacProvider) {
rc, err := ip.LoadIacDir(e.dirPath)
scanRespChan <- dirScanResp{err, rc}
}(iacP)
}

for i := 0; i < len(e.iacProviders); i++ {
sr := <-scanRespChan
merr = multierror.Append(merr, sr.err)
// deduplication of resources
if len(resourceConfig) > 0 {
for key, r := range sr.rc {
updateResourceConfigs(key, r, resourceConfig)
}
} else {
for key := range sr.rc {
resourceConfig[key] = append(resourceConfig[key], sr.rc[key]...)
}
}
}

// get all resource configs in the directory
resourceConfig, merr = e.getResourceConfigs()
} else {
// create results output from Iac provider
// iac providers will contain one element
Expand All @@ -202,39 +177,20 @@ func (e *Executor) Execute() (results Output, err error) {
}

// for the iac providers that don't implement sub folder scanning
// return the error (existing behavior)
// return the error to the caller
if !implementsSubFolderScan(e.iacType) {
if err := merr.ErrorOrNil(); err != nil {
return results, err
}
}

// update results with resource config
results.ResourceConfig = resourceConfig

// evaluate policies
results.Violations = policy.EngineOutput{}
violations := results.Violations.AsViolationStore()

// channel for engine evaluation result
evalResultChan := make(chan engineEvalResult)

for _, engine := range e.policyEngines {
go func(eng policy.Engine) {
output, err := eng.Evaluate(policy.EngineInput{InputData: &results.ResourceConfig})
evalResultChan <- engineEvalResult{err, output}
}(engine)
}

for i := 0; i < len(e.policyEngines); i++ {
evalR := <-evalResultChan
if evalR.err != nil {
return results, evalR.err
}
violations = violations.Add(evalR.output.AsViolationStore())
if err := e.findViolations(&results); err != nil {
return results, err
}

results.Violations = policy.EngineOutputFromViolationStore(&violations)

resourcePath := e.filePath
if resourcePath == "" {
resourcePath = e.dirPath
Expand All @@ -260,26 +216,66 @@ func (e *Executor) Execute() (results Output, err error) {
return results, nil
}

// updateResourceConfigs adds a resource of given type if it is not present in allResources
func updateResourceConfigs(resourceType string, resources []output.ResourceConfig, allResources output.AllResourceConfigs) {
for _, res := range resources {
if !isConfigPresent(allResources[resourceType], res) {
allResources[resourceType] = append(allResources[resourceType], res)
// getResourceConfigs is a helper method to get all resource configs
func (e *Executor) getResourceConfigs() (output.AllResourceConfigs, *multierror.Error) {
var merr *multierror.Error
resourceConfig := make(output.AllResourceConfigs)

// channel for directory scan response
scanRespChan := make(chan dirScanResp)

// create results output from Iac provider[s]
for _, iacP := range e.iacProviders {
go func(ip iacProvider.IacProvider) {
rc, err := ip.LoadIacDir(e.dirPath)
scanRespChan <- dirScanResp{err, rc}
}(iacP)
}

for i := 0; i < len(e.iacProviders); i++ {
sr := <-scanRespChan
merr = multierror.Append(merr, sr.err)
// deduplication of resources
if len(resourceConfig) > 0 {
for key, r := range sr.rc {
utils.UpdateResourceConfigs(key, r, resourceConfig)
}
} else {
for key := range sr.rc {
resourceConfig[key] = append(resourceConfig[key], sr.rc[key]...)
}
}
}

return resourceConfig, merr
}

// isConfigPresent checks whether a resource is already present in the list of configs or not
// the equality of a resource is based on name, source and config of the resource
func isConfigPresent(resources []output.ResourceConfig, resourceConfig output.ResourceConfig) bool {
for _, resource := range resources {
if resource.Name == resourceConfig.Name && resource.Source == resourceConfig.Source {
if reflect.DeepEqual(resource.Config, resourceConfig.Config) {
return true
}
// findViolations is a helper method to find all violations in the resource config
func (e *Executor) findViolations(results *Output) error {
// evaluate policies
results.Violations = policy.EngineOutput{}
violations := results.Violations.AsViolationStore()

// channel for engine evaluation result
evalResultChan := make(chan engineEvalResult)

for _, engine := range e.policyEngines {
go func(eng policy.Engine) {
output, err := eng.Evaluate(policy.EngineInput{InputData: &results.ResourceConfig})
evalResultChan <- engineEvalResult{err, output}
}(engine)
}

for i := 0; i < len(e.policyEngines); i++ {
evalR := <-evalResultChan
if evalR.err != nil {
return evalR.err
}
violations = violations.Add(evalR.output.AsViolationStore())
}
return false

results.Violations = policy.EngineOutputFromViolationStore(&violations)
return nil
}

// implementsSubFolderScan checks if given iac type supports sub folder scanning
Expand Down
23 changes: 23 additions & 0 deletions pkg/utils/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package utils

import (
"fmt"
"reflect"
"strings"

"github.com/accurics/terrascan/pkg/iac-providers/output"
Expand Down Expand Up @@ -61,3 +62,25 @@ func GetResourceCount(resourceMapping map[string][]output.ResourceConfig) (count
}
return
}

// UpdateResourceConfigs adds a resource of given type if it is not present in allResources
func UpdateResourceConfigs(resourceType string, resources []output.ResourceConfig, allResources output.AllResourceConfigs) {
for _, res := range resources {
if !IsConfigPresent(allResources[resourceType], res) {
allResources[resourceType] = append(allResources[resourceType], res)
}
}
}

// IsConfigPresent checks whether a resource is already present in the list of configs or not.
// The equality of a resource is based on name, source and config of the resource.
func IsConfigPresent(resources []output.ResourceConfig, resourceConfig output.ResourceConfig) bool {
for _, resource := range resources {
if resource.Name == resourceConfig.Name && resource.Source == resourceConfig.Source {
if reflect.DeepEqual(resource.Config, resourceConfig.Config) {
return true
}
}
}
return false
}
32 changes: 14 additions & 18 deletions test/e2e/scan/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,22 @@ var _ = Describe("Scan", func() {
Describe("scan command is run without any flags", func() {
Context("when no iac type is provided, terrascan scans with all iac providers", func() {
Context("no tf files are present in the working directory", func() {
When("log level is not set to debug", func() {
It("scan the directory and display results", func() {
scanArgs := []string{scanUtils.ScanCommand}
session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...)
helper.ValidateExitCode(session, scanUtils.ScanTimeout, helper.ExitCodeZero)
})
Context("iac loading errors would be displayed in the output", func() {
It("scan the directory and display results", func() {
scanArgs := []string{scanUtils.ScanCommand}
scanArgs := []string{scanUtils.ScanCommand, "-o", "json", "-p", policyRootRelPath}
// these errors would come from terraform, helm, and kustomize iac providers
errString1 := "has no terraform config files"
errString2 := "kustomization.y(a)ml file not found in the directory"
errString3 := "no helm charts found in directory"
session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...)
helper.ValidateExitCode(session, scanUtils.ScanTimeout, helper.ExitCodeZero)
})
})
Context("iac loading errors would be displayed in the output, when log level is debug", func() {
When("log level is set to debug", func() {
It("scan the directory and display results", func() {
scanArgs := []string{scanUtils.ScanCommand, "-o", "json", "-l", "debug", "-p", policyRootRelPath}
// these errors would come from terraform, helm, and kustomize iac providers
errString1 := "has no terraform config files"
errString2 := "kustomization.y(a)ml file not found in the directory"
errString3 := "no helm charts found in directory"
session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...)
helper.ContainsDirScanErrorSubString(session, errString1)
helper.ContainsDirScanErrorSubString(session, errString2)
helper.ContainsDirScanErrorSubString(session, errString3)
})
helper.ContainsDirScanErrorSubString(session, errString1)
helper.ContainsDirScanErrorSubString(session, errString2)
helper.ContainsDirScanErrorSubString(session, errString3)
})
})
})
Expand Down

0 comments on commit c8bf725

Please sign in to comment.