Skip to content

Commit 5ac6ff4

Browse files
authored
Merge pull request #50 from pd/walkmodulecalls
Implement `WalkModuleCalls`
2 parents 2029af0 + 709fcda commit 5ac6ff4

File tree

9 files changed

+335
-13
lines changed

9 files changed

+335
-13
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: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package helper
22

33
import (
4+
"github.com/hashicorp/go-version"
45
"github.com/hashicorp/hcl/v2"
56
"github.com/hashicorp/hcl/v2/gohcl"
67
"github.com/terraform-linters/tflint-plugin-sdk/terraform"
@@ -135,6 +136,37 @@ func (r *Runner) WalkResources(resourceType string, walker func(*terraform.Resou
135136
return nil
136137
}
137138

139+
// WalkModuleCalls visits all module calls from Files.
140+
func (r *Runner) WalkModuleCalls(walker func(*terraform.ModuleCall) error) error {
141+
for _, file := range r.Files {
142+
calls, _, diags := file.Body.PartialContent(&hcl.BodySchema{
143+
Blocks: []hcl.BlockHeaderSchema{
144+
{
145+
Type: "module",
146+
LabelNames: []string{"name"},
147+
},
148+
},
149+
})
150+
if diags.HasErrors() {
151+
return diags
152+
}
153+
154+
for _, block := range calls.Blocks {
155+
call, diags := simpleDecodeModuleCallBlock(block)
156+
if diags.HasErrors() {
157+
return diags
158+
}
159+
160+
err := walker(call)
161+
if err != nil {
162+
return err
163+
}
164+
}
165+
}
166+
167+
return nil
168+
}
169+
138170
// Backend returns the terraform backend configuration.
139171
func (r *Runner) Backend() (*terraform.Backend, error) {
140172
for _, file := range r.Files {
@@ -251,21 +283,10 @@ func simpleDecodeResouceBlock(resource *hcl.Block) (*terraform.Resource, hcl.Dia
251283

252284
var ref *terraform.ProviderConfigRef
253285
if attr, exists := content.Attributes["provider"]; exists {
254-
traversal, diags := hcl.AbsTraversalForExpr(attr.Expr)
286+
ref, diags = decodeProviderConfigRef(attr.Expr)
255287
if diags.HasErrors() {
256288
return nil, diags
257289
}
258-
259-
ref = &terraform.ProviderConfigRef{
260-
Name: traversal.RootName(),
261-
NameRange: traversal[0].SourceRange(),
262-
}
263-
264-
if len(traversal) > 1 {
265-
aliasStep := traversal[1].(hcl.TraverseAttr)
266-
ref.Alias = aliasStep.Name
267-
ref.AliasRange = aliasStep.SourceRange().Ptr()
268-
}
269290
}
270291

271292
managed := &terraform.ManagedResource{}
@@ -379,3 +400,109 @@ func simpleDecodeResouceBlock(resource *hcl.Block) (*terraform.Resource, hcl.Dia
379400
TypeRange: resource.LabelRanges[0],
380401
}, nil
381402
}
403+
404+
func simpleDecodeModuleCallBlock(block *hcl.Block) (*terraform.ModuleCall, hcl.Diagnostics) {
405+
content, remain, diags := block.Body.PartialContent(&hcl.BodySchema{
406+
Attributes: []hcl.AttributeSchema{
407+
{Name: "source", Required: true},
408+
{Name: "version"},
409+
{Name: "providers"},
410+
},
411+
})
412+
if diags.HasErrors() {
413+
return nil, diags
414+
}
415+
416+
var sourceAddr string
417+
var sourceAddrRange hcl.Range
418+
if attr, exists := content.Attributes["source"]; exists {
419+
if diags := gohcl.DecodeExpression(attr.Expr, nil, &sourceAddr); diags.HasErrors() {
420+
return nil, diags
421+
}
422+
sourceAddrRange = attr.Expr.Range()
423+
}
424+
425+
providers := []terraform.PassedProviderConfig{}
426+
if attr, exists := content.Attributes["providers"]; exists {
427+
pairs, diags := hcl.ExprMap(attr.Expr)
428+
if diags.HasErrors() {
429+
return nil, diags
430+
}
431+
432+
for _, pair := range pairs {
433+
key, diags := decodeProviderConfigRef(pair.Key)
434+
if diags.HasErrors() {
435+
return nil, diags
436+
}
437+
438+
value, diags := decodeProviderConfigRef(pair.Value)
439+
if diags.HasErrors() {
440+
return nil, diags
441+
}
442+
443+
providers = append(providers, terraform.PassedProviderConfig{
444+
InChild: key,
445+
InParent: value,
446+
})
447+
}
448+
}
449+
450+
var versionRequired version.Constraints
451+
var versionValue string
452+
var versionRange hcl.Range
453+
var err error
454+
if attr, exists := content.Attributes["version"]; exists {
455+
versionRange = attr.Expr.Range()
456+
457+
if diags := gohcl.DecodeExpression(attr.Expr, nil, &versionValue); diags.HasErrors() {
458+
return nil, diags
459+
}
460+
461+
versionRequired, err = version.NewConstraint(versionValue)
462+
if err != nil {
463+
return nil, hcl.Diagnostics{
464+
{Severity: hcl.DiagError, Summary: "Invalid version constraint"},
465+
}
466+
}
467+
}
468+
469+
return &terraform.ModuleCall{
470+
Name: block.Labels[0],
471+
472+
SourceAddr: sourceAddr,
473+
SourceAddrRange: sourceAddrRange,
474+
SourceSet: !sourceAddrRange.Empty(),
475+
476+
Config: remain,
477+
ConfigRange: block.DefRange,
478+
479+
Version: terraform.VersionConstraint{
480+
Required: versionRequired,
481+
DeclRange: versionRange,
482+
},
483+
484+
Providers: providers,
485+
486+
DeclRange: block.DefRange,
487+
}, nil
488+
}
489+
490+
func decodeProviderConfigRef(expr hcl.Expression) (*terraform.ProviderConfigRef, hcl.Diagnostics) {
491+
traversal, diags := hcl.AbsTraversalForExpr(expr)
492+
if diags.HasErrors() {
493+
return nil, diags
494+
}
495+
496+
ref := &terraform.ProviderConfigRef{
497+
Name: traversal.RootName(),
498+
NameRange: traversal[0].SourceRange(),
499+
}
500+
501+
if len(traversal) > 1 {
502+
aliasStep := traversal[1].(hcl.TraverseAttr)
503+
ref.Alias = aliasStep.Name
504+
ref.AliasRange = aliasStep.SourceRange().Ptr()
505+
}
506+
507+
return ref, nil
508+
}

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")

0 commit comments

Comments
 (0)