Skip to content

Commit f4dbe3b

Browse files
committed
Implement WalkModuleCalls
This allows rulesets to add lints that enforce rules on module calls rather than only on resource declarations. I plan to use it, for instance, to constrain the different forms of addresses used for module sources. Version constraints may be a bit different over the wire: the type exposed by `go-version` can't necessarily be exactly reconstructed, as they've been normalized under the hood. The original value is still accessible through the usual `hcl.Body.PartialContent` or similar functions, if needed. In Terraform 0.12, I think the `Count` and `ForEach` struct members will never actually be set, but the corresponding Terraform types have them, so I've included them here. Maybe they'll Just Work once 0.13 is out of beta and support is added to tflint.
1 parent 2029af0 commit f4dbe3b

File tree

9 files changed

+201
-1
lines changed

9 files changed

+201
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/google/go-cmp v0.5.0
77
github.com/hashicorp/go-hclog v0.14.1
88
github.com/hashicorp/go-plugin v1.3.0
9+
github.com/hashicorp/go-version v1.0.0
910
github.com/hashicorp/hcl/v2 v2.6.0
1011
github.com/zclconf/go-cty v1.5.1
1112
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQ
3939
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
4040
github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8=
4141
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
42+
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
43+
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
4244
github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o=
4345
github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
4446
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=

helper/runner.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ func (r *Runner) WalkResources(resourceType string, walker func(*terraform.Resou
135135
return nil
136136
}
137137

138+
// WalkModuleCalls visits all module calls from Files.
139+
func (r *Runner) WalkModuleCalls(walker func(*terraform.ModuleCall) error) error {
140+
return nil
141+
}
142+
138143
// Backend returns the terraform backend configuration.
139144
func (r *Runner) Backend() (*terraform.Backend, error) {
140145
for _, file := range r.Files {

terraform/configs.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package terraform
22

3-
import "github.com/hashicorp/hcl/v2"
3+
import (
4+
"github.com/hashicorp/go-version"
5+
"github.com/hashicorp/hcl/v2"
6+
)
47

58
// Resource is an alternative representation of configs.Resource.
69
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/resource.go#L13-L33
@@ -39,6 +42,13 @@ type ManagedResource struct {
3942
PreventDestroySet bool
4043
}
4144

45+
// PassedProviderConfig is an alternative representation of configs.PassedProviderConfig.
46+
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/module_call.go#L155-L158
47+
type PassedProviderConfig struct {
48+
InChild *ProviderConfigRef
49+
InParent *ProviderConfigRef
50+
}
51+
4252
// ProviderConfigRef is an alternative representation of configs.ProviderConfigRef.
4353
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/resource.go#L371-L376
4454
type ProviderConfigRef struct {
@@ -79,6 +89,40 @@ type Backend struct {
7989
DeclRange hcl.Range
8090
}
8191

92+
// ModuleCall is an alternative representation of configs.ModuleCall.
93+
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/module_call.go#L11-L31
94+
// DependsOn is not supported due to the difficulty of intermediate representation.
95+
type ModuleCall struct {
96+
Name string
97+
98+
SourceAddr string
99+
SourceAddrRange hcl.Range
100+
SourceSet bool
101+
102+
Config hcl.Body
103+
ConfigRange hcl.Range
104+
105+
Version VersionConstraint
106+
107+
Count hcl.Expression
108+
CountRange hcl.Range
109+
ForEach hcl.Expression
110+
ForEachRange hcl.Range
111+
112+
Providers []PassedProviderConfig
113+
114+
// DependsOn []hcl.Traversal
115+
116+
DeclRange hcl.Range
117+
}
118+
119+
// VersionConstraint is an alternative representation of configs.VersionConstraint.
120+
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/version_constraint.go#L16-L19
121+
type VersionConstraint struct {
122+
Required version.Constraints
123+
DeclRange hcl.Range
124+
}
125+
82126
// ProvisionerWhen is an alternative representation of configs.ProvisionerWhen.
83127
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/provisioner.go#L172-L181
84128
type ProvisionerWhen int

tflint/client/client.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,33 @@ func (c *Client) WalkResources(resource string, walker func(*terraform.Resource)
103103
return nil
104104
}
105105

106+
// WalkModuleCalls calls the server-side ModuleCalls method and passed the decode response
107+
// to the passed function.
108+
func (c *Client) WalkModuleCalls(walker func(*terraform.ModuleCall) error) error {
109+
log.Printf("[DEBUG] WalkModuleCalls")
110+
111+
var response ModuleCallsResponse
112+
if err := c.rpcClient.Call("Plugin.ModuleCalls", ModuleCallsRequest{}, &response); err != nil {
113+
return err
114+
}
115+
if response.Err != nil {
116+
return response.Err
117+
}
118+
119+
for _, c := range response.ModuleCalls {
120+
call, diags := decodeModuleCall(c)
121+
if diags.HasErrors() {
122+
return diags
123+
}
124+
125+
if err := walker(call); err != nil {
126+
return err
127+
}
128+
}
129+
130+
return nil
131+
}
132+
106133
// Backend calls the server-side Backend method and returns the backend configuration.
107134
func (c *Client) Backend() (*terraform.Backend, error) {
108135
log.Printf("[DEBUG] Backend")

tflint/client/decode.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"strings"
66

7+
"github.com/hashicorp/go-version"
78
hcl "github.com/hashicorp/hcl/v2"
89
"github.com/hashicorp/hcl/v2/hclsyntax"
910
"github.com/terraform-linters/tflint-plugin-sdk/terraform"
@@ -165,6 +166,113 @@ func decodeManagedResource(resource *ManagedResource) (*terraform.ManagedResourc
165166
}, nil
166167
}
167168

169+
// ModuleCall is an intermediate representation of terraform.ModuleCall.
170+
type ModuleCall struct {
171+
Name string
172+
173+
SourceAddr string
174+
SourceAddrRange hcl.Range
175+
SourceSet bool
176+
177+
Version string
178+
VersionRange hcl.Range
179+
180+
Config []byte
181+
ConfigRange hcl.Range
182+
183+
Count []byte
184+
CountRange hcl.Range
185+
ForEach []byte
186+
ForEachRange hcl.Range
187+
188+
Providers []PassedProviderConfig
189+
DeclRange hcl.Range
190+
191+
// DependsOn []hcl.Traversal
192+
}
193+
194+
// PassedProviderConfig is an intermediate representation of terraform.PassedProviderConfig.
195+
type PassedProviderConfig struct {
196+
InChild *terraform.ProviderConfigRef
197+
InParent *terraform.ProviderConfigRef
198+
}
199+
200+
func decodeModuleCall(call *ModuleCall) (*terraform.ModuleCall, hcl.Diagnostics) {
201+
file, diags := parseConfig(call.Config, call.ConfigRange.Filename, call.ConfigRange.Start)
202+
if diags.HasErrors() {
203+
return nil, diags
204+
}
205+
206+
var count hcl.Expression
207+
if call.Count != nil {
208+
count, diags = parseExpression(call.Count, call.CountRange.Filename, call.CountRange.Start)
209+
if diags.HasErrors() {
210+
return nil, diags
211+
}
212+
}
213+
214+
var forEach hcl.Expression
215+
if call.ForEach != nil {
216+
forEach, diags = parseExpression(call.ForEach, call.ForEachRange.Filename, call.ForEachRange.Start)
217+
if diags.HasErrors() {
218+
return nil, diags
219+
}
220+
}
221+
222+
providers := []terraform.PassedProviderConfig{}
223+
for _, provider := range call.Providers {
224+
providers = append(providers, terraform.PassedProviderConfig{
225+
InChild: provider.InChild,
226+
InParent: provider.InParent,
227+
})
228+
}
229+
230+
versionConstraint := terraform.VersionConstraint{DeclRange: call.VersionRange}
231+
if !call.VersionRange.Empty() {
232+
required, err := version.NewConstraint(call.Version)
233+
if err != nil {
234+
detail := fmt.Sprintf(
235+
"ModuleCall '%s' version constraint '%s' parse error: %s",
236+
call.Name,
237+
call.Version,
238+
err,
239+
)
240+
241+
return nil, hcl.Diagnostics{
242+
&hcl.Diagnostic{
243+
Severity: hcl.DiagError,
244+
Summary: "Failed to reparse module version constraint",
245+
Detail: detail,
246+
Subject: &call.VersionRange,
247+
},
248+
}
249+
}
250+
251+
versionConstraint.Required = required
252+
}
253+
254+
return &terraform.ModuleCall{
255+
Name: call.Name,
256+
257+
SourceAddr: call.SourceAddr,
258+
SourceAddrRange: call.SourceAddrRange,
259+
SourceSet: call.SourceSet,
260+
261+
Config: file.Body,
262+
ConfigRange: call.ConfigRange,
263+
264+
Version: versionConstraint,
265+
266+
Count: count,
267+
CountRange: call.CountRange,
268+
ForEach: forEach,
269+
ForEachRange: call.ForEachRange,
270+
271+
Providers: providers,
272+
DeclRange: call.DeclRange,
273+
}, nil
274+
}
275+
168276
// Backend is an intermediate representation of terraform.Backend.
169277
type Backend struct {
170278
Type string

tflint/client/rpc.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ type BlocksResponse struct {
3838
Err error
3939
}
4040

41+
// ModuleCallsRequest is a request to the server-side ModuleCalls method.
42+
type ModuleCallsRequest struct{}
43+
44+
// ModuleCallsResponse is a response to the server-side ModuleCalls method.
45+
type ModuleCallsResponse struct {
46+
ModuleCalls []*ModuleCall
47+
Err error
48+
}
49+
4150
// ResourcesRequest is a request to the server-side Resources method.
4251
type ResourcesRequest struct {
4352
Name string

tflint/interface.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ type Runner interface {
2020
// You must pass a resource type as the first argument.
2121
WalkResources(string, func(*terraform.Resource) error) error
2222

23+
// WalkModuleCalls visits module calls with the passed function.
24+
WalkModuleCalls(func(*terraform.ModuleCall) error) error
25+
2326
// Backend returns the backend configuration, if any.
2427
Backend() (*terraform.Backend, error)
2528

tflint/server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type Server interface {
77
Attributes(*client.AttributesRequest, *client.AttributesResponse) error
88
Blocks(*client.BlocksRequest, *client.BlocksResponse) error
99
Resources(*client.ResourcesRequest, *client.ResourcesResponse) error
10+
ModuleCalls(*client.ModuleCallsRequest, *client.ModuleCallsResponse) error
1011
Backend(*client.BackendRequest, *client.BackendResponse) error
1112
EvalExpr(*client.EvalExprRequest, *client.EvalExprResponse) error
1213
EmitIssue(*client.EmitIssueRequest, *interface{}) error

0 commit comments

Comments
 (0)