Skip to content

Commit

Permalink
terraform 0.15 support
Browse files Browse the repository at this point in the history
  • Loading branch information
devang-gaur committed Jun 14, 2021
1 parent 617ef74 commit a0eae26
Show file tree
Hide file tree
Showing 61 changed files with 2,899 additions and 11 deletions.
15 changes: 8 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ replace (
)

require (
github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect
github.com/awslabs/goformation/v4 v4.19.1
github.com/ghodss/yaml v1.0.0
github.com/google/uuid v1.2.0 // indirect
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-getter v1.5.1
github.com/hashicorp/go-multierror v1.1.0
github.com/hashicorp/go-getter v1.5.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-retryablehttp v0.6.6
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/hcl/v2 v2.8.2
github.com/hashicorp/terraform v0.14.4
github.com/hashicorp/hcl/v2 v2.10.0
github.com/hashicorp/terraform v0.15.3
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734
github.com/iancoleman/strcase v0.1.3
github.com/itchyny/gojq v0.12.1
Expand All @@ -36,10 +37,10 @@ require (
github.com/pkg/errors v0.9.1
github.com/spf13/afero v1.5.1
github.com/spf13/cobra v1.1.1
github.com/zclconf/go-cty v1.8.2
github.com/zclconf/go-cty v1.8.3
go.uber.org/zap v1.16.0
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b
golang.org/x/tools v0.1.2 // indirect
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273
golang.org/x/tools v0.1.3 // indirect
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
helm.sh/helm/v3 v3.4.0
Expand Down
150 changes: 150 additions & 0 deletions go.sum

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pkg/iac-providers/terraform.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package iacprovider

import (
tfv15 "github.com/accurics/terrascan/pkg/iac-providers/terraform/v15"
"reflect"

tfv12 "github.com/accurics/terrascan/pkg/iac-providers/terraform/v12"
Expand All @@ -13,7 +14,8 @@ const (
terraformV12 supportedIacVersion = "v12"
terraformV13 supportedIacVersion = "v13"
terraformV14 supportedIacVersion = "v14"
terraformDefaultVersion = terraformV14
terraformV15 supportedIacVersion = "v15"
terraformDefaultVersion = terraformV15
)

// register terraform as an IaC provider with terrascan
Expand All @@ -22,4 +24,5 @@ func init() {
RegisterIacProvider(terraform, terraformV12, terraformDefaultVersion, reflect.TypeOf(tfv12.TfV12{}))
RegisterIacProvider(terraform, terraformV13, terraformDefaultVersion, reflect.TypeOf(tfv14.TfV14{}))
RegisterIacProvider(terraform, terraformV14, terraformDefaultVersion, reflect.TypeOf(tfv14.TfV14{}))
RegisterIacProvider(terraform, terraformV15, terraformDefaultVersion, reflect.TypeOf(tfv15.TfV15{}))
}
30 changes: 30 additions & 0 deletions pkg/iac-providers/terraform/v15/load-dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright (C) 2020 Accurics, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tfv15

import (
commons "github.com/accurics/terrascan/pkg/iac-providers/terraform/commons"

"github.com/accurics/terrascan/pkg/iac-providers/output"
)

// LoadIacDir starts traversing from the given rootDir and traverses through
// all the descendant modules present to create an output list of all the
// resources present in rootDir and descendant modules
func (*TfV15) LoadIacDir(absRootDir string, nonRecursive bool) (allResourcesConfig output.AllResourceConfigs, err error) {
return commons.NewTerraformDirectoryLoader(absRootDir, nonRecursive).LoadIacDir()
}
298 changes: 298 additions & 0 deletions pkg/iac-providers/terraform/v15/load-dir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
/*
Copyright (C) 2020 Accurics, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tfv15

import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"

"github.com/accurics/terrascan/pkg/iac-providers/output"
commons_test "github.com/accurics/terrascan/pkg/iac-providers/terraform/commons/test"
"github.com/accurics/terrascan/pkg/utils"
"github.com/hashicorp/go-multierror"
)

func TestLoadIacDir(t *testing.T) {
var nilMultiErr *multierror.Error = nil

testErrorMessage := fmt.Sprintf(`failed to load terraform config dir '%s'. error from terraform:
%s:1,21-2,1: Invalid block definition; A block definition must have block content delimited by "{" and "}", starting on the same line as the block header.
%s:1,1-5: Unsupported block type; Blocks of type "some" are not expected here.
`, testDataDir, emptyTfFilePath, emptyTfFilePath)

errStringInvalidModuleConfigs := fmt.Sprintf(`failed to build unified config. errors:
<nil>: Failed to read module directory; Module directory %s does not exist or cannot be read.
`, filepath.Join(testDataDir, "invalid-moduleconfigs", "cloudfront", "sub-cloudfront"))

errStringDependsOnDir := fmt.Sprintf(`failed to build unified config. errors:
<nil>: Failed to read module directory; Module directory %s does not exist or cannot be read.
<nil>: Failed to read module directory; Module directory %s does not exist or cannot be read.
`, filepath.Join(testDataDir, "depends_on", "live", "log"), filepath.Join(testDataDir, "depends_on", "live", "security"))

errStringModuleSourceInvalid := fmt.Sprintf(`failed to build unified config. errors:
<nil>: Invalid module config directory; Module directory '%s' has no terraform config files for module cloudfront
<nil>: Invalid module config directory; Module directory '%s' has no terraform config files for module m1
`, filepath.Join(testDataDir, "invalid-module-source"), filepath.Join(testDataDir, "invalid-module-source"))

testDirPath1 := "not-there"
testDirPath2 := filepath.Join(testDataDir, "testfile")
invalidDirErrStringTemplate := "directory '%s' has no terraform config files"

pathErr := &os.PathError{Op: "lstat", Path: "not-there", Err: syscall.ENOENT}
if utils.IsWindowsPlatform() {
pathErr = &os.PathError{Op: "CreateFile", Path: "not-there", Err: syscall.ENOENT}
}

table := []struct {
name string
dirPath string
tfv15 TfV15
want output.AllResourceConfigs
nonRecursive bool
wantErr error
}{
{
name: "invalid dirPath",
dirPath: testDirPath1,
tfv15: TfV15{},
nonRecursive: true,
wantErr: multierror.Append(fmt.Errorf(invalidDirErrStringTemplate, testDirPath1)),
},
{
name: "invalid dirPath recursive",
dirPath: testDirPath1,
tfv15: TfV15{},
wantErr: multierror.Append(pathErr),
},
{
name: "empty config",
dirPath: testDirPath2,
tfv15: TfV15{},
nonRecursive: true,
wantErr: multierror.Append(fmt.Errorf(invalidDirErrStringTemplate, testDirPath2)),
},
{
name: "empty config recursive",
dirPath: testDirPath2,
tfv15: TfV15{},
wantErr: nilMultiErr,
},
{
name: "incorrect module structure",
dirPath: filepath.Join(testDataDir, "invalid-moduleconfigs"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: multierror.Append(fmt.Errorf("failed to build terraform allResourcesConfig")),
},
{
name: "incorrect module structure recursive",
dirPath: filepath.Join(testDataDir, "invalid-moduleconfigs"),
tfv15: TfV15{},
// same error is loaded two times because, both root module and a child module will generated same error
wantErr: multierror.Append(fmt.Errorf(errStringInvalidModuleConfigs), fmt.Errorf(errStringInvalidModuleConfigs)),
},
{
name: "load invalid config dir",
dirPath: testDataDir,
tfv15: TfV15{},
nonRecursive: true,
wantErr: multierror.Append(fmt.Errorf(testErrorMessage)),
},
{
name: "load invalid config dir recursive",
dirPath: testDataDir,
tfv15: TfV15{},
wantErr: multierror.Append(fmt.Errorf(testErrorMessage),
fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "deep-modules", "modules")),
fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "deep-modules", "modules", "m4", "modules")),
fmt.Errorf(errStringDependsOnDir),
fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "invalid-module-source")),
fmt.Errorf(errStringModuleSourceInvalid),
fmt.Errorf(errStringInvalidModuleConfigs),
fmt.Errorf(errStringInvalidModuleConfigs),
fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "relative-moduleconfigs")),
fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "tfjson")),
),
},
{
name: "invalid module source directory",
dirPath: filepath.Join(testDataDir, "invalid-module-source", "invalid_source"),
tfv15: TfV15{},
wantErr: multierror.Append(fmt.Errorf(errStringModuleSourceInvalid)),
},
{
name: "provider block with only alias",
dirPath: filepath.Join(testDataDir, "provider-with-only-alias"),
tfv15: TfV15{},
wantErr: nilMultiErr,
},
{
name: "required provider with configuration aliases",
dirPath: filepath.Join(testDataDir, "required-providers", "configuration-alias"),
tfv15: TfV15{},
wantErr: nilMultiErr,
},

}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
_, gotErr := tt.tfv15.LoadIacDir(tt.dirPath, tt.nonRecursive)
me, ok := gotErr.(*multierror.Error)
if !ok {
t.Errorf("expected multierror.Error, got %T", gotErr)
}
if tt.wantErr == nilMultiErr {
if err := me.ErrorOrNil(); err != nil {
t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr)
}
} else if me.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr)
}
})
}

tfJSONDir := filepath.Join(testDataDir, "tfjson")
nestedModuleErr1 := fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "deep-modules", "modules"))
nestedModuleErr2 := fmt.Errorf(invalidDirErrStringTemplate, filepath.Join(testDataDir, "deep-modules", "modules", "m4", "modules"))

table2 := []struct {
name string
tfConfigDir string
tfJSONFile string
tfv15 TfV15
nonRecursive bool
wantErr error
}{
{
name: "config1",
tfConfigDir: filepath.Join(testDataDir, "tfconfigs"),
tfJSONFile: filepath.Join(tfJSONDir, "fullconfig.json"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: nilMultiErr,
},
{
name: "config1 recursive",
tfConfigDir: filepath.Join(testDataDir, "tfconfigs"),
// no change in the output expected as the config dir doesn't contain subfolder
tfJSONFile: filepath.Join(tfJSONDir, "fullconfig.json"),
tfv15: TfV15{},
wantErr: nilMultiErr,
},
{
name: "module directory",
tfConfigDir: filepath.Join(testDataDir, "moduleconfigs"),
tfJSONFile: filepath.Join(tfJSONDir, "moduleconfigs.json"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: nilMultiErr,
},
{
name: "module directory recursive",
tfConfigDir: filepath.Join(testDataDir, "moduleconfigs"),
// no change in the output expected as the config dir doesn't contain subfolder
tfJSONFile: filepath.Join(tfJSONDir, "moduleconfigs.json"),
tfv15: TfV15{},
wantErr: nilMultiErr,
},
{
name: "nested module directory",
tfConfigDir: filepath.Join(testDataDir, "deep-modules"),
tfJSONFile: filepath.Join(tfJSONDir, "deep-modules.json"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: nilMultiErr,
},
{
name: "nested module directory recursive",
tfConfigDir: filepath.Join(testDataDir, "deep-modules"),
tfJSONFile: filepath.Join(tfJSONDir, "deep-modules-recursive.json"),
tfv15: TfV15{},
wantErr: multierror.Append(nestedModuleErr1, nestedModuleErr2),
},
{
name: "complex variables",
tfConfigDir: filepath.Join(testDataDir, "complex-variables"),
tfJSONFile: filepath.Join(tfJSONDir, "complex-variables.json"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: nilMultiErr,
},
{
name: "recursive loop while resolving variables",
tfConfigDir: filepath.Join(testDataDir, "recursive-loop-variables"),
tfJSONFile: filepath.Join(tfJSONDir, "recursive-loop-variables.json"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: nilMultiErr,
},
{
name: "recursive loop while resolving locals",
tfConfigDir: filepath.Join(testDataDir, "recursive-loop-locals"),
tfJSONFile: filepath.Join(tfJSONDir, "recursive-loop-locals.json"),
tfv15: TfV15{},
nonRecursive: true,
wantErr: nilMultiErr,
},
}

for _, tt := range table2 {
t.Run(tt.name, func(t *testing.T) {
got, gotErr := tt.tfv15.LoadIacDir(tt.tfConfigDir, tt.nonRecursive)
me, ok := gotErr.(*multierror.Error)
if !ok {
t.Errorf("expected multierror.Error, got %T", gotErr)
}
if tt.wantErr == nilMultiErr {
if err := me.ErrorOrNil(); err != nil {
t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr)
}
} else if me.Error() != tt.wantErr.Error() {
t.Errorf("unexpected error; gotErr: '%v', wantErr: '%v'", gotErr, tt.wantErr)
}

var want output.AllResourceConfigs

// Read the expected value and unmarshal into want
contents, _ := ioutil.ReadFile(tt.tfJSONFile)
if utils.IsWindowsPlatform() {
contents = utils.ReplaceWinNewLineBytes(contents)
}

err := json.Unmarshal(contents, &want)
if err != nil {
t.Errorf("unexpected error unmarshalling want: %v", err)
}

match, err := commons_test.IdenticalAllResourceConfigs(got, want)
if err != nil {
t.Errorf("unexpected error checking result: %v", err)
}
if !match {
g, _ := json.MarshalIndent(got, "", " ")
w, _ := json.MarshalIndent(want, "", " ")
t.Errorf("got '%v', want: '%v'", string(g), string(w))
}
})
}
}
Loading

0 comments on commit a0eae26

Please sign in to comment.