Skip to content

Commit 70d4a1e

Browse files
committed
Add Runner.Backend()
Closes #46. This allows rulesets developed as plugins to add lint rules based on the configuration of the state backend.
1 parent eede5b9 commit 70d4a1e

File tree

7 files changed

+129
-0
lines changed

7 files changed

+129
-0
lines changed

terraform/configs.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ type Connection struct {
6969
DeclRange hcl.Range
7070
}
7171

72+
// Backend is an alternative representation of configs.Backend.
73+
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/backend.go#L12-L18
74+
type Backend struct {
75+
Type string
76+
Config hcl.Body
77+
ConfigRange hcl.Range
78+
TypeRange hcl.Range
79+
DeclRange hcl.Range
80+
}
81+
7282
// ProvisionerWhen is an alternative representation of configs.ProvisionerWhen.
7383
// https://github.com/hashicorp/terraform/blob/v0.12.26/configs/provisioner.go#L172-L181
7484
type ProvisionerWhen int

tflint/client/client.go

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

106+
// Backend calls the server-side Backend method and returns the backend configuration.
107+
func (c *Client) Backend() (*terraform.Backend, error) {
108+
log.Printf("[DEBUG] Backend")
109+
110+
var response BackendResponse
111+
if err := c.rpcClient.Call("Plugin.Backend", BackendRequest{}, &response); err != nil {
112+
return nil, err
113+
}
114+
if response.Err != nil {
115+
return nil, response.Err
116+
}
117+
118+
backend, diags := decodeBackend(response.Backend)
119+
if diags.HasErrors() {
120+
return nil, diags
121+
}
122+
123+
return backend, nil
124+
}
125+
106126
// EvaluateExpr calls the server-side EvalExpr method and reflects the response
107127
// in the passed argument.
108128
func (c *Client) EvaluateExpr(expr hcl.Expression, ret interface{}) error {

tflint/client/client_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ func (*mockServer) Resources(req *ResourcesRequest, resp *ResourcesResponse) err
8585
return nil
8686
}
8787

88+
func (*mockServer) Backend(req *BackendRequest, resp *BackendResponse) error {
89+
*resp = BackendResponse{Backend: &Backend{
90+
Type: "example",
91+
Config: []byte(`storage = "cloud"`),
92+
ConfigRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}},
93+
DeclRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 22}},
94+
TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 11}, End: hcl.Pos{Line: 2, Column: 19}},
95+
}, Err: nil}
96+
return nil
97+
}
98+
8899
func (*mockServer) EvalExpr(req *EvalExprRequest, resp *EvalExprResponse) error {
89100
*resp = EvalExprResponse{Val: cty.StringVal("1"), Err: nil}
90101
return nil
@@ -150,6 +161,53 @@ func Test_WalkResourceAttributes(t *testing.T) {
150161
}
151162
}
152163

164+
func Test_Backend(t *testing.T) {
165+
client, server := startMockServer(t)
166+
defer server.Listener.Close()
167+
168+
expected := &terraform.Backend{
169+
Type: "example",
170+
Config: &hclsyntax.Body{
171+
Attributes: hclsyntax.Attributes{
172+
"storage": {
173+
Name: "storage",
174+
Expr: &hclsyntax.TemplateExpr{
175+
Parts: []hclsyntax.Expression{
176+
&hclsyntax.LiteralValueExpr{
177+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 16}, End: hcl.Pos{Line: 3, Column: 21}},
178+
},
179+
},
180+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 15}, End: hcl.Pos{Line: 3, Column: 22}},
181+
},
182+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}},
183+
NameRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 12}},
184+
EqualsRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 13}, End: hcl.Pos{Line: 3, Column: 14}},
185+
},
186+
},
187+
Blocks: hclsyntax.Blocks{},
188+
SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}},
189+
EndRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 22}, End: hcl.Pos{Line: 3, Column: 22}},
190+
},
191+
ConfigRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}},
192+
DeclRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 22}},
193+
TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 11}, End: hcl.Pos{Line: 2, Column: 19}},
194+
}
195+
196+
backend, err := client.Backend()
197+
if err != nil {
198+
t.Fatal(err)
199+
}
200+
201+
opts := []cmp.Option{
202+
cmpopts.IgnoreUnexported(hclsyntax.Body{}),
203+
cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val"),
204+
cmpopts.IgnoreFields(hcl.Pos{}, "Byte"),
205+
}
206+
if !cmp.Equal(expected, backend, opts...) {
207+
t.Fatalf("Diff: %s", cmp.Diff(expected, backend, opts...))
208+
}
209+
}
210+
153211
func Test_WalkResourceBlocks(t *testing.T) {
154212
client, server := startMockServer(t)
155213
defer server.Listener.Close()

tflint/client/decode.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,34 @@ func decodeManagedResource(resource *ManagedResource) (*terraform.ManagedResourc
165165
}, nil
166166
}
167167

168+
// Backend is an intermediate representation of terraform.Backend.
169+
type Backend struct {
170+
Type string
171+
Config []byte
172+
ConfigRange hcl.Range
173+
TypeRange hcl.Range
174+
DeclRange hcl.Range
175+
}
176+
177+
func decodeBackend(backend *Backend) (*terraform.Backend, hcl.Diagnostics) {
178+
if backend == nil {
179+
return nil, nil
180+
}
181+
182+
file, diags := parseConfig(backend.Config, backend.ConfigRange.Filename, backend.ConfigRange.Start)
183+
if diags.HasErrors() {
184+
return nil, diags
185+
}
186+
187+
return &terraform.Backend{
188+
Type: backend.Type,
189+
Config: file.Body,
190+
TypeRange: backend.TypeRange,
191+
DeclRange: backend.DeclRange,
192+
ConfigRange: backend.ConfigRange,
193+
}, nil
194+
}
195+
168196
// Connection is an intermediate representation of terraform.Connection.
169197
type Connection struct {
170198
Config []byte

tflint/client/rpc.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ type AttributesResponse struct {
1717
Err error
1818
}
1919

20+
// BackendRequest is a request to the server-side Backend method.
21+
type BackendRequest struct{}
22+
23+
// BackendResponse is a response to the server-side Backend method.
24+
type BackendResponse struct {
25+
Backend *Backend
26+
Err error
27+
}
28+
2029
// BlocksRequest is a request to the server-side Blocks method.
2130
type BlocksRequest struct {
2231
Resource 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+
// Backend returns the backend configuration, if any.
24+
Backend() (*terraform.Backend, error)
25+
2326
// EvaluateExpr evaluates the passed expression and reflects the result in ret.
2427
// Since this function returns an application error, it is expected to use the EnsureNoError
2528
// to determine whether to continue processing.

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+
Backend(*client.BackendRequest, *client.BackendResponse) error
1011
EvalExpr(*client.EvalExprRequest, *client.EvalExprResponse) error
1112
EmitIssue(*client.EmitIssueRequest, *interface{}) error
1213
}

0 commit comments

Comments
 (0)