diff --git a/README.md b/README.md index d7fc222..2afca3a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is a template repository for building a custom ruleset. You can create a pl ## Requirements -- TFLint v0.18+ +- TFLint v0.19+ - Go v1.14 ## Installation @@ -26,6 +26,7 @@ plugin "template" { |aws_s3_bucket_example_lifecycle_rule|Example rule for accessing top-level/nested blocks and attributes under blocks|ERROR|✔|| |local_file_example_provisioner|Example rule for accessing reserved attributes/blocks such as "provisioner"|ERROR|✔|| |terraform_backend_type|Example rule for accessing the backend configuration|ERROR|✔|| +|module_call_validity|Example rule for accessing module calls|ERROR|✔|| ## Building the plugin diff --git a/go.mod b/go.mod index 0a623bf..3788814 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/terraform-linters/tflint-ruleset-template go 1.14 require ( + github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/hcl/v2 v2.6.0 - github.com/terraform-linters/tflint-plugin-sdk v0.3.0 + github.com/terraform-linters/tflint-plugin-sdk v0.4.0 ) diff --git a/go.sum b/go.sum index 7e604fb..74d374f 100644 --- a/go.sum +++ b/go.sum @@ -31,13 +31,15 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8= github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= @@ -73,8 +75,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/terraform-linters/tflint-plugin-sdk v0.3.0 h1:TUMBlM17mZKMzaZtp1KLj6T6BHLTunVQ/8f2cWOaMjY= -github.com/terraform-linters/tflint-plugin-sdk v0.3.0/go.mod h1:QoSqSV/8GSOrQy3OStK3EEdsA3yZm13My4BQcnx3Zic= +github.com/terraform-linters/tflint-plugin-sdk v0.4.0 h1:KkOEd1viXz4ZkQnFEzDtcOWu1ZpGfs3gwUbHEJEsbc8= +github.com/terraform-linters/tflint-plugin-sdk v0.4.0/go.mod h1:8ambsL6uszTTnQ9vFOXuUABmd/40ji4YWbKmB/hIYGI= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= diff --git a/rules/module_call_validity.go b/rules/module_call_validity.go new file mode 100644 index 0000000..9bc149c --- /dev/null +++ b/rules/module_call_validity.go @@ -0,0 +1,56 @@ +package rules + +import ( + "github.com/hashicorp/go-version" + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/terraform" + "github.com/terraform-linters/tflint-plugin-sdk/tflint" +) + +// ModuleCallValidityRule checks whether ... +type ModuleCallValidityRule struct{} + +// NewModuleCallValidityRule returns a new rule +func NewModuleCallValidityRule() *ModuleCallValidityRule { + return &ModuleCallValidityRule{} +} + +// Name returns the rule name +func (r *ModuleCallValidityRule) Name() string { + return "module_call_validity" +} + +// Enabled returns whether the rule is enabled by default +func (r *ModuleCallValidityRule) Enabled() bool { + return true +} + +// Severity returns the rule severity +func (r *ModuleCallValidityRule) Severity() string { + return tflint.ERROR +} + +// Link returns the rule reference link +func (r *ModuleCallValidityRule) Link() string { + return "" +} + +// Check checks whether ... +func (r *ModuleCallValidityRule) Check(runner tflint.Runner) error { + return runner.WalkModuleCalls(func(call *terraform.ModuleCall) error { + if call.SourceAddr != "acceptable/source" { + return runner.EmitIssue(r, "unacceptable module source", call.SourceAddrRange) + } + + if len(call.Providers) != 0 { + return runner.EmitIssue(r, "must not pass providers", hcl.Range{}) + } + + expectedVersion, _ := version.NewVersion("0.1.0") + if !call.Version.Required.Check(expectedVersion) { + return runner.EmitIssue(r, "must accept version 0.1.0", call.Version.DeclRange) + } + + return nil + }) +} diff --git a/rules/module_calls_validity_test.go b/rules/module_calls_validity_test.go new file mode 100644 index 0000000..122775d --- /dev/null +++ b/rules/module_calls_validity_test.go @@ -0,0 +1,83 @@ +package rules + +import ( + "testing" + + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/helper" +) + +func Test_ModuleCallValidity(t *testing.T) { + cases := []struct { + Name string + Content string + Expected helper.Issues + }{ + { + Name: "module source issue", + Content: ` +module "foo" { + source = "in/correct" +}`, + Expected: helper.Issues{ + { + Rule: NewModuleCallValidityRule(), + Message: "unacceptable module source", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 3, Column: 12}, + End: hcl.Pos{Line: 3, Column: 24}, + }, + }, + }, + }, + + { + Name: "providers issue", + Content: ` +module "foo" { + source = "acceptable/source" + providers = { aws.dns = aws.east } +}`, + Expected: helper.Issues{ + { + Rule: NewModuleCallValidityRule(), + Message: "must not pass providers", + Range: hcl.Range{}, + }, + }, + }, + + { + Name: "version constraint", + Content: ` +module "foo" { + source = "acceptable/source" + version = ">= 1.0.0" +}`, + Expected: helper.Issues{ + { + Rule: NewModuleCallValidityRule(), + Message: "must accept version 0.1.0", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 4, Column: 13}, + End: hcl.Pos{Line: 4, Column: 23}, + }, + }, + }, + }, + } + + rule := NewModuleCallValidityRule() + + for _, tc := range cases { + runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) + + if err := rule.Check(runner); err != nil { + t.Fatalf("Unexpected error occurred: %s", err) + } + + helper.AssertIssues(t, tc.Expected, runner.Issues) + } +}