Skip to content

Commit

Permalink
add unit tests for runtime package and refactor accordingly
Browse files Browse the repository at this point in the history
  • Loading branch information
Yusuf Kanchwala committed Jul 26, 2020
1 parent 1fac82b commit fb4e51a
Show file tree
Hide file tree
Showing 5 changed files with 369 additions and 84 deletions.
128 changes: 44 additions & 84 deletions pkg/runtime/executor.go
Original file line number Diff line number Diff line change
@@ -1,131 +1,91 @@
package runtime

import (
"fmt"
"os"

"github.com/accurics/terrascan/pkg/utils"
"go.uber.org/zap"

CloudProvider "github.com/accurics/terrascan/pkg/cloud-providers"
IacProvider "github.com/accurics/terrascan/pkg/iac-providers"
cloudProvider "github.com/accurics/terrascan/pkg/cloud-providers"
iacProvider "github.com/accurics/terrascan/pkg/iac-providers"
"github.com/accurics/terrascan/pkg/iac-providers/output"
)

// Executor object
type Executor struct {
filePath string
dirPath string
cloudType string
iacType string
iacVersion string
filePath string
dirPath string
cloudType string
iacType string
iacVersion string
iacProvider iacProvider.IacProvider
cloudProvider cloudProvider.CloudProvider
}

// NewExecutor creates a runtime object
func NewExecutor(iacType, iacVersion, cloudType, filePath, dirPath string) *Executor {
return &Executor{
func NewExecutor(iacType, iacVersion, cloudType, filePath, dirPath string) (e *Executor, err error) {
e = &Executor{
filePath: filePath,
dirPath: dirPath,
cloudType: cloudType,
iacType: iacType,
iacVersion: iacVersion,
}
}

// ValidateInputs validates the inputs to the executor object
func (r *Executor) ValidateInputs() error {

// error message
errMsg := "input validation failed"

// terrascan can accept either a file or a directory, both inputs cannot
// be processed together
if r.filePath != "" && r.dirPath != "" {
zap.S().Errorf("cannot accept both '-f %s' and '-d %s' options together", r.filePath, r.dirPath)
return fmt.Errorf(errMsg)
}

if r.dirPath != "" {
// if directory, check if directory exists
absDirPath, err := utils.GetAbsPath(r.dirPath)
if err != nil {
return err
}

if _, err := os.Stat(absDirPath); err != nil {
zap.S().Errorf("directory '%s' does not exist", absDirPath)
return fmt.Errorf(errMsg)
}
zap.S().Debugf("directory '%s' exists", absDirPath)
} else {

// if file path, check if file exists
absFilePath, err := utils.GetAbsPath(r.filePath)
if err != nil {
return fmt.Errorf(errMsg)
}

if _, err := os.Stat(absFilePath); err != nil {
zap.S().Errorf("file '%s' does not exist", absFilePath)
return fmt.Errorf(errMsg)
}
zap.S().Debugf("file '%s' exists", absFilePath)
// initialized executor
if err = e.Init(); err != nil {
return e, err
}

// check if Iac type is supported
if !IacProvider.IsIacSupported(r.iacType, r.iacVersion) {
zap.S().Errorf("iac type '%s', version '%s' not supported", r.iacType, r.iacVersion)
return fmt.Errorf(errMsg)
}
zap.S().Debugf("iac type '%s', version '%s' is supported", r.iacType, r.iacVersion)

// check if cloud type is supported
if !CloudProvider.IsCloudSupported(r.cloudType) {
zap.S().Errorf("cloud type '%s' not supported", r.cloudType)
return fmt.Errorf(errMsg)
}
zap.S().Debugf("cloud type '%s' supported", r.cloudType)

// check if policy type is supported

// successful
zap.S().Debug("input validation successful")
return nil
return e, nil
}

// Execute validates the inputs, processes the IaC, creates json output
func (r *Executor) Execute() error {
// Init validates input and initializes iac and cloud providers
func (e *Executor) Init() error {

// validate inputs
if err := r.ValidateInputs(); err != nil {
err := e.ValidateInputs()
if err != nil {
return err
}

// create new IacProvider
iacProvider, err := IacProvider.NewIacProvider(r.iacType, r.iacVersion)
e.iacProvider, err = iacProvider.NewIacProvider(e.iacType, e.iacVersion)
if err != nil {
zap.S().Errorf("failed to create a new IacProvider for iacType '%s'. error: '%s'", r.iacType, err)
zap.S().Errorf("failed to create a new IacProvider for iacType '%s'. error: '%s'", e.iacType, err)
return err
}

var iacOut output.AllResourceConfigs
if r.dirPath != "" {
iacOut, err = iacProvider.LoadIacDir(r.dirPath)
} else {
// create config from IaC
iacOut, err = iacProvider.LoadIacFile(r.filePath)
}
// create new CloudProvider
e.cloudProvider, err = cloudProvider.NewCloudProvider(e.cloudType)
if err != nil {
zap.S().Errorf("failed to create a new CloudProvider for cloudType '%s'. error: '%s'", e.cloudType, err)
return err
}

// create new CloudProvider
cloudProvider, err := CloudProvider.NewCloudProvider(r.cloudType)
return nil
}

// Execute validates the inputs, processes the IaC, creates json output
func (e *Executor) Execute() error {

// load iac config
var (
iacOut output.AllResourceConfigs
err error
)
if e.dirPath != "" {
iacOut, err = e.iacProvider.LoadIacDir(e.dirPath)
} else {
// create config from IaC
iacOut, err = e.iacProvider.LoadIacFile(e.filePath)
}
if err != nil {
zap.S().Errorf("failed to create a new CloudProvider for cloudType '%s'. error: '%s'", r.cloudType, err)
return err
}
normalized, err := cloudProvider.CreateNormalizedJSON(iacOut)

// create normalized json
normalized, err := e.cloudProvider.CreateNormalizedJSON(iacOut)
if err != nil {
return err
}
Expand Down
133 changes: 133 additions & 0 deletions pkg/runtime/executor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package runtime

import (
"fmt"
"reflect"
"testing"

cloudProvider "github.com/accurics/terrascan/pkg/cloud-providers"
awsProvider "github.com/accurics/terrascan/pkg/cloud-providers/aws"
iacProvider "github.com/accurics/terrascan/pkg/iac-providers"
"github.com/accurics/terrascan/pkg/iac-providers/output"
tfv12 "github.com/accurics/terrascan/pkg/iac-providers/terraform/v12"
)

var (
errMockLoadIacDir = fmt.Errorf("mock LoadIacDir")
errMockLoadIacFile = fmt.Errorf("mock LoadIacFile")
errMockCreateNormalizedJSON = fmt.Errorf("mock CreateNormalizedJSON")
)

// MockIacProvider mocks IacProvider interface
type MockIacProvider struct {
output output.AllResourceConfigs
err error
}

func (m MockIacProvider) LoadIacDir(dir string) (output.AllResourceConfigs, error) {
return m.output, m.err
}

func (m MockIacProvider) LoadIacFile(file string) (output.AllResourceConfigs, error) {
return m.output, m.err
}

// MockCloudProvider mocks CloudProvider interface
type MockCloudProvider struct {
err error
}

func (m MockCloudProvider) CreateNormalizedJSON(data output.AllResourceConfigs) (mockInterface interface{}, err error) {
return data, m.err
}

func TestExecute(t *testing.T) {

table := []struct {
name string
executor Executor
wantErr error
}{
{
name: "test LoadIacDir",
executor: Executor{
dirPath: "./testdata/testdir",
iacProvider: MockIacProvider{err: errMockLoadIacDir},
},
wantErr: errMockLoadIacDir,
},
{
name: "test LoadIacFile",
executor: Executor{
filePath: "./testdata/testfile",
iacProvider: MockIacProvider{err: errMockLoadIacFile},
},
wantErr: errMockLoadIacFile,
},
{
name: "test CreateNormalizedJSON error",
executor: Executor{
filePath: "./testdata/testfile",
iacProvider: MockIacProvider{err: nil},
cloudProvider: MockCloudProvider{err: errMockCreateNormalizedJSON},
},
wantErr: errMockCreateNormalizedJSON,
},
{
name: "test CreateNormalizedJSON",
executor: Executor{
filePath: "./testdata/testfile",
iacProvider: MockIacProvider{err: nil},
cloudProvider: MockCloudProvider{err: nil},
},
wantErr: nil,
},
}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
gotErr := tt.executor.Execute()
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr)
}
})
}
}

func TestInit(t *testing.T) {

table := []struct {
name string
executor Executor
wantErr error
wantIacProvider iacProvider.IacProvider
wantCloudProvider cloudProvider.CloudProvider
}{
{
name: "valid filePath",
executor: Executor{
filePath: "./testdata/testfile",
dirPath: "",
cloudType: "aws",
iacType: "terraform",
iacVersion: "v12",
},
wantErr: nil,
wantIacProvider: &tfv12.TfV12{},
wantCloudProvider: &awsProvider.AWSProvider{},
},
}

for _, tt := range table {
gotErr := tt.executor.Init()
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr)
}
if !reflect.DeepEqual(tt.executor.iacProvider, tt.wantIacProvider) {
t.Errorf("got: '%v', want: '%v'", tt.executor.iacProvider, tt.wantIacProvider)
}
if !reflect.DeepEqual(tt.executor.cloudProvider, tt.wantCloudProvider) {
t.Errorf("got: '%v', want: '%v'", tt.executor.cloudProvider, tt.wantCloudProvider)
}
}
}
Empty file added pkg/runtime/testdata/testfile
Empty file.
82 changes: 82 additions & 0 deletions pkg/runtime/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package runtime

import (
"fmt"
"os"

"github.com/accurics/terrascan/pkg/utils"
"go.uber.org/zap"

CloudProvider "github.com/accurics/terrascan/pkg/cloud-providers"
IacProvider "github.com/accurics/terrascan/pkg/iac-providers"
)

var (
errEmptyIacPath = fmt.Errorf("empty iac path, either use '-f' or '-d' option")
errIncorrectIacPath = fmt.Errorf("cannot accept both '-f' and '-d' options together")
errDirNotExists = fmt.Errorf("directory does not exist")
errFileNotExists = fmt.Errorf("file does not exist")
errIacNotSupported = fmt.Errorf("iac type or version not supported")
errCloudNotSupported = fmt.Errorf("cloud type not supported")
)

// ValidateInputs validates the inputs to the executor object
func (e *Executor) ValidateInputs() error {

// terrascan can accept either a file or a directory
if e.filePath == "" && e.dirPath == "" {
zap.S().Errorf("no IaC path specified; use '-f' for file or '-d' for directory")
return errEmptyIacPath
}
if e.filePath != "" && e.dirPath != "" {
zap.S().Errorf("cannot accept both '-f %s' and '-d %s' options together", e.filePath, e.dirPath)
return errIncorrectIacPath
}

if e.dirPath != "" {
// if directory, check if directory exists
absDirPath, err := utils.GetAbsPath(e.dirPath)
if err != nil {
return err
}

if _, err := os.Stat(absDirPath); err != nil {
zap.S().Errorf("directory '%s' does not exist", absDirPath)
return errDirNotExists
}
zap.S().Debugf("directory '%s' exists", absDirPath)
} else {

// if file path, check if file exists
absFilePath, err := utils.GetAbsPath(e.filePath)
if err != nil {
return err
}

if _, err := os.Stat(absFilePath); err != nil {
zap.S().Errorf("file '%s' does not exist", absFilePath)
return errFileNotExists
}
zap.S().Debugf("file '%s' exists", absFilePath)
}

// check if Iac type is supported
if !IacProvider.IsIacSupported(e.iacType, e.iacVersion) {
zap.S().Errorf("iac type '%s', version '%s' not supported", e.iacType, e.iacVersion)
return errIacNotSupported
}
zap.S().Debugf("iac type '%s', version '%s' is supported", e.iacType, e.iacVersion)

// check if cloud type is supported
if !CloudProvider.IsCloudSupported(e.cloudType) {
zap.S().Errorf("cloud type '%s' not supported", e.cloudType)
return errCloudNotSupported
}
zap.S().Debugf("cloud type '%s' supported", e.cloudType)

// check if policy type is supported

// successful
zap.S().Debug("input validation successful")
return nil
}
Loading

0 comments on commit fb4e51a

Please sign in to comment.