Skip to content

Commit b8bc40c

Browse files
committed
client: Add intermediate representations and decode methods
1 parent bc9d299 commit b8bc40c

File tree

3 files changed

+463
-22
lines changed

3 files changed

+463
-22
lines changed

tflint/client/decode.go

Lines changed: 218 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,193 @@ import (
88
hcl "github.com/hashicorp/hcl/v2"
99
"github.com/hashicorp/hcl/v2/hclsyntax"
1010
"github.com/terraform-linters/tflint-plugin-sdk/terraform"
11+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs"
12+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/configs"
13+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/experiments"
1114
)
1215

16+
// Config is an intermediate representation of configs.Config.
17+
type Config struct {
18+
Path addrs.Module
19+
Module *Module
20+
CallRange hcl.Range
21+
SourceAddr string
22+
SourceAddrRange hcl.Range
23+
Version string
24+
}
25+
26+
func decodeConfig(config *Config) (*configs.Config, hcl.Diagnostics) {
27+
module, diags := decodeModule(config.Module)
28+
if diags.HasErrors() {
29+
return nil, diags
30+
}
31+
32+
var ver *version.Version
33+
var err error
34+
if config.Version != "" {
35+
ver, err = version.NewVersion(config.Version)
36+
if err != nil {
37+
return nil, hcl.Diagnostics{
38+
&hcl.Diagnostic{
39+
Severity: hcl.DiagError,
40+
Summary: "Failed to reparse version",
41+
Detail: err.Error(),
42+
},
43+
}
44+
}
45+
}
46+
47+
return &configs.Config{
48+
Path: config.Path,
49+
Module: module,
50+
CallRange: config.CallRange,
51+
SourceAddr: config.SourceAddr,
52+
SourceAddrRange: config.SourceAddrRange,
53+
Version: ver,
54+
}, nil
55+
}
56+
57+
// Module is an intermediate representation of configs.Module.
58+
type Module struct {
59+
SourceDir string
60+
61+
CoreVersionConstraints []string
62+
CoreVersionConstraintRanges []hcl.Range
63+
64+
ActiveExperiments experiments.Set
65+
66+
Backend *Backend
67+
ProviderConfigs map[string]*Provider
68+
ProviderRequirements *RequiredProviders
69+
ProviderLocalNames map[terraform.Provider]string
70+
ProviderMetas map[terraform.Provider]*ProviderMeta
71+
72+
Variables map[string]*Variable
73+
Locals map[string]*Local
74+
Outputs map[string]*Output
75+
76+
ModuleCalls map[string]*ModuleCall
77+
78+
ManagedResources map[string]*Resource
79+
DataResources map[string]*Resource
80+
}
81+
82+
func decodeModule(module *Module) (*configs.Module, hcl.Diagnostics) {
83+
versionConstraints := make([]terraform.VersionConstraint, len(module.CoreVersionConstraints))
84+
for i, v := range module.CoreVersionConstraints {
85+
constraint, diags := parseVersionConstraint(v, module.CoreVersionConstraintRanges[i])
86+
if diags.HasErrors() {
87+
return nil, diags
88+
}
89+
versionConstraints[i] = constraint
90+
}
91+
92+
backend, diags := decodeBackend(module.Backend)
93+
if diags.HasErrors() {
94+
return nil, diags
95+
}
96+
97+
providers := map[string]*configs.Provider{}
98+
for k, v := range module.ProviderConfigs {
99+
p, diags := decodeProvider(v)
100+
if diags.HasErrors() {
101+
return nil, diags
102+
}
103+
providers[k] = p
104+
}
105+
106+
requirements, diags := decodeRequiredProviders(module.ProviderRequirements)
107+
if diags.HasErrors() {
108+
return nil, diags
109+
}
110+
111+
metas := map[terraform.Provider]*configs.ProviderMeta{}
112+
for k, v := range module.ProviderMetas {
113+
m, diags := decodeProviderMeta(v)
114+
if diags.HasErrors() {
115+
return nil, diags
116+
}
117+
metas[k] = m
118+
}
119+
120+
variables := map[string]*configs.Variable{}
121+
for k, v := range module.Variables {
122+
variable, diags := decodeVariable(v)
123+
if diags.HasErrors() {
124+
return nil, diags
125+
}
126+
variables[k] = variable
127+
}
128+
129+
locals := map[string]*configs.Local{}
130+
for k, v := range module.Locals {
131+
l, diags := decodeLocal(v)
132+
if diags.HasErrors() {
133+
return nil, diags
134+
}
135+
locals[k] = l
136+
}
137+
138+
outputs := map[string]*configs.Output{}
139+
for k, v := range module.Outputs {
140+
o, diags := decodeOutput(v)
141+
if diags.HasErrors() {
142+
return nil, diags
143+
}
144+
outputs[k] = o
145+
}
146+
147+
calls := map[string]*terraform.ModuleCall{}
148+
for k, v := range module.ModuleCalls {
149+
c, diags := decodeModuleCall(v)
150+
if diags.HasErrors() {
151+
return nil, diags
152+
}
153+
calls[k] = c
154+
}
155+
156+
managed := map[string]*terraform.Resource{}
157+
for k, v := range module.ManagedResources {
158+
r, diags := decodeResource(v)
159+
if diags.HasErrors() {
160+
return nil, diags
161+
}
162+
managed[k] = r
163+
}
164+
165+
data := map[string]*terraform.Resource{}
166+
for k, v := range module.DataResources {
167+
d, diags := decodeResource(v)
168+
if diags.HasErrors() {
169+
return nil, diags
170+
}
171+
data[k] = d
172+
}
173+
174+
return &configs.Module{
175+
SourceDir: module.SourceDir,
176+
177+
CoreVersionConstraints: versionConstraints,
178+
179+
ActiveExperiments: module.ActiveExperiments,
180+
181+
Backend: backend,
182+
ProviderConfigs: providers,
183+
ProviderRequirements: requirements,
184+
ProviderLocalNames: module.ProviderLocalNames,
185+
ProviderMetas: metas,
186+
187+
Variables: variables,
188+
Locals: locals,
189+
Outputs: outputs,
190+
191+
ModuleCalls: calls,
192+
193+
ManagedResources: managed,
194+
DataResources: data,
195+
}, nil
196+
}
197+
13198
// Attribute is an intermediate representation of hcl.Attribute.
14199
type Attribute struct {
15200
Name string
@@ -141,6 +326,10 @@ type ManagedResource struct {
141326
}
142327

143328
func decodeManagedResource(resource *ManagedResource) (*terraform.ManagedResource, hcl.Diagnostics) {
329+
if resource == nil {
330+
return nil, nil
331+
}
332+
144333
connection, diags := decodeConnection(resource.Connection)
145334
if diags.HasErrors() {
146335
return nil, diags
@@ -229,28 +418,9 @@ func decodeModuleCall(call *ModuleCall) (*terraform.ModuleCall, hcl.Diagnostics)
229418
})
230419
}
231420

232-
versionConstraint := terraform.VersionConstraint{DeclRange: call.VersionRange}
233-
if !call.VersionRange.Empty() {
234-
required, err := version.NewConstraint(call.Version)
235-
if err != nil {
236-
detail := fmt.Sprintf(
237-
"ModuleCall '%s' version constraint '%s' parse error: %s",
238-
call.Name,
239-
call.Version,
240-
err,
241-
)
242-
243-
return nil, hcl.Diagnostics{
244-
&hcl.Diagnostic{
245-
Severity: hcl.DiagError,
246-
Summary: "Failed to reparse module version constraint",
247-
Detail: detail,
248-
Subject: &call.VersionRange,
249-
},
250-
}
251-
}
252-
253-
versionConstraint.Required = required
421+
versionConstraint, diags := parseVersionConstraint(call.Version, call.VersionRange)
422+
if diags.HasErrors() {
423+
return nil, diags
254424
}
255425

256426
return &terraform.ModuleCall{
@@ -394,3 +564,29 @@ func parseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Dia
394564

395565
panic(fmt.Sprintf("Unexpected file: %s", filename))
396566
}
567+
568+
func parseVersionConstraint(versionStr string, versionRange hcl.Range) (terraform.VersionConstraint, hcl.Diagnostics) {
569+
versionConstraint := terraform.VersionConstraint{DeclRange: versionRange}
570+
if !versionRange.Empty() {
571+
required, err := version.NewConstraint(versionStr)
572+
if err != nil {
573+
detail := fmt.Sprintf(
574+
"Version constraint '%s' parse error: %s",
575+
versionStr,
576+
err,
577+
)
578+
579+
return versionConstraint, hcl.Diagnostics{
580+
&hcl.Diagnostic{
581+
Severity: hcl.DiagError,
582+
Summary: "Failed to reparse version constraint",
583+
Detail: detail,
584+
Subject: &versionRange,
585+
},
586+
}
587+
}
588+
589+
versionConstraint.Required = required
590+
}
591+
return versionConstraint, nil
592+
}

tflint/client/decode_named_values.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package client
2+
3+
import (
4+
hcl "github.com/hashicorp/hcl/v2"
5+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/configs"
6+
"github.com/zclconf/go-cty/cty"
7+
)
8+
9+
// Variable is an intermediate representation of configs.Variable.
10+
type Variable struct {
11+
Name string
12+
Description string
13+
Default cty.Value
14+
Type cty.Type
15+
ParsingMode configs.VariableParsingMode
16+
Validations []*VariableValidation
17+
18+
DescriptionSet bool
19+
20+
DeclRange hcl.Range
21+
}
22+
23+
func decodeVariable(variable *Variable) (*configs.Variable, hcl.Diagnostics) {
24+
ret := make([]*configs.VariableValidation, len(variable.Validations))
25+
for i, v := range variable.Validations {
26+
validation, diags := decodeVariableValidation(v)
27+
if diags.HasErrors() {
28+
return nil, diags
29+
}
30+
ret[i] = validation
31+
}
32+
33+
return &configs.Variable{
34+
Name: variable.Name,
35+
Description: variable.Description,
36+
Default: variable.Default,
37+
Type: variable.Type,
38+
ParsingMode: variable.ParsingMode,
39+
Validations: ret,
40+
41+
DescriptionSet: variable.DescriptionSet,
42+
43+
DeclRange: variable.DeclRange,
44+
}, nil
45+
}
46+
47+
// VariableValidation is an intermediate representation of configs.VariableValidation.
48+
type VariableValidation struct {
49+
Condition []byte
50+
ConditionRange hcl.Range
51+
52+
ErrorMessage string
53+
54+
DeclRange hcl.Range
55+
}
56+
57+
func decodeVariableValidation(validation *VariableValidation) (*configs.VariableValidation, hcl.Diagnostics) {
58+
expr, diags := parseExpression(validation.Condition, validation.ConditionRange.Filename, validation.ConditionRange.Start)
59+
if diags.HasErrors() {
60+
return nil, diags
61+
}
62+
63+
return &configs.VariableValidation{
64+
Condition: expr,
65+
ErrorMessage: validation.ErrorMessage,
66+
DeclRange: validation.DeclRange,
67+
}, nil
68+
}
69+
70+
// Local is an intermediate representation of configs.Local.
71+
type Local struct {
72+
Name string
73+
Expr []byte
74+
ExprRange hcl.Range
75+
76+
DeclRange hcl.Range
77+
}
78+
79+
func decodeLocal(local *Local) (*configs.Local, hcl.Diagnostics) {
80+
expr, diags := parseExpression(local.Expr, local.ExprRange.Filename, local.ExprRange.Start)
81+
if diags.HasErrors() {
82+
return nil, diags
83+
}
84+
85+
return &configs.Local{
86+
Name: local.Name,
87+
Expr: expr,
88+
89+
DeclRange: local.DeclRange,
90+
}, nil
91+
}
92+
93+
// Output is an intermediate representation of configs.Output.
94+
type Output struct {
95+
Name string
96+
Description string
97+
Expr []byte
98+
ExprRange hcl.Range
99+
// DependsOn []hcl.Traversal
100+
Sensitive bool
101+
102+
DescriptionSet bool
103+
SensitiveSet bool
104+
105+
DeclRange hcl.Range
106+
}
107+
108+
func decodeOutput(output *Output) (*configs.Output, hcl.Diagnostics) {
109+
expr, diags := parseExpression(output.Expr, output.ExprRange.Filename, output.ExprRange.Start)
110+
if diags.HasErrors() {
111+
return nil, diags
112+
}
113+
114+
return &configs.Output{
115+
Name: output.Name,
116+
Description: output.Description,
117+
Expr: expr,
118+
Sensitive: output.Sensitive,
119+
120+
DescriptionSet: output.DescriptionSet,
121+
SensitiveSet: output.Sensitive,
122+
123+
DeclRange: output.DeclRange,
124+
}, nil
125+
}

0 commit comments

Comments
 (0)