Skip to content

Commit

Permalink
Merge pull request #24 from terraform-linters/text_transmission_over_rpc
Browse files Browse the repository at this point in the history
tflint: Sending expression nodes as a text representation
  • Loading branch information
wata727 committed Jun 13, 2020
2 parents 18bced3 + 74f4303 commit a1fcc54
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 49 deletions.
26 changes: 0 additions & 26 deletions plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"net/rpc"

plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
)

Expand Down Expand Up @@ -36,28 +34,4 @@ func (RuleSetPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, er
// the type of the related structure is registered in gob at the initial time.
func init() {
gob.Register(tflint.Error{})
// https://github.com/hashicorp/hcl/blob/v2.0.0/hclsyntax/expression.go
gob.Register(&hclsyntax.LiteralValueExpr{})
gob.Register(&hclsyntax.ScopeTraversalExpr{})
gob.Register(&hclsyntax.RelativeTraversalExpr{})
gob.Register(&hclsyntax.FunctionCallExpr{})
gob.Register(&hclsyntax.ConditionalExpr{})
gob.Register(&hclsyntax.IndexExpr{})
gob.Register(&hclsyntax.TupleConsExpr{})
gob.Register(&hclsyntax.ObjectConsExpr{})
gob.Register(&hclsyntax.ObjectConsKeyExpr{})
gob.Register(&hclsyntax.ForExpr{})
gob.Register(&hclsyntax.SplatExpr{})
// https://github.com/hashicorp/hcl/blob/v2.0.0/hclsyntax/expression_ops.go
gob.Register(&hclsyntax.BinaryOpExpr{})
gob.Register(&hclsyntax.UnaryOpExpr{})
// https://github.com/hashicorp/hcl/blob/v2.0.0/hclsyntax/expression_template.go
gob.Register(&hclsyntax.TemplateExpr{})
gob.Register(&hclsyntax.TemplateJoinExpr{})
gob.Register(&hclsyntax.TemplateWrapExpr{})
// https://github.com/hashicorp/hcl/blob/v2.0.0/traversal.go
gob.Register(hcl.TraverseRoot{})
gob.Register(hcl.TraverseAttr{})
gob.Register(hcl.TraverseIndex{})
gob.Register(hcl.TraverseSplat{})
}
85 changes: 72 additions & 13 deletions tflint/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package tflint

import (
"fmt"
"io/ioutil"
"log"
"net"
"net/rpc"
"strings"

hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
Expand All @@ -30,10 +33,20 @@ type AttributesRequest struct {

// AttributesResponse is the interface used to communicate via RPC.
type AttributesResponse struct {
Attributes []*hcl.Attribute
Attributes []*Attribute
Err error
}

// Attribute is an intermediate representation of hcl.Attribute.
// It has an expression as a string of bytes so that hcl.Expression is not transferred via RPC.
type Attribute struct {
Name string
Expr []byte
ExprRange hcl.Range
Range hcl.Range
NameRange hcl.Range
}

// WalkResourceAttributes queries the host process, receives a list of attributes that match the conditions,
// and passes each to the walker function.
func (c *Client) WalkResourceAttributes(resource, attributeName string, walker func(*hcl.Attribute) error) error {
Expand All @@ -48,7 +61,18 @@ func (c *Client) WalkResourceAttributes(resource, attributeName string, walker f
}

for _, attribute := range response.Attributes {
if err := walker(attribute); err != nil {
expr, diags := parseExpression(attribute.Expr, attribute.ExprRange.Filename, attribute.ExprRange.Start)
if diags.HasErrors() {
return diags
}
attr := &hcl.Attribute{
Name: attribute.Name,
Expr: expr,
Range: attribute.Range,
NameRange: attribute.NameRange,
}

if err := walker(attr); err != nil {
return err
}
}
Expand All @@ -58,8 +82,9 @@ func (c *Client) WalkResourceAttributes(resource, attributeName string, walker f

// EvalExprRequest is the interface used to communicate via RPC.
type EvalExprRequest struct {
Expr hcl.Expression
Ret interface{}
Expr []byte
ExprRange hcl.Range
Ret interface{}
}

// EvalExprResponse is the interface used to communicate with RPC.
Expand All @@ -74,7 +99,16 @@ func (c *Client) EvaluateExpr(expr hcl.Expression, ret interface{}) error {
var response EvalExprResponse
var err error

if err := c.rpcClient.Call("Plugin.EvalExpr", EvalExprRequest{Expr: expr, Ret: ret}, &response); err != nil {
src, err := ioutil.ReadFile(expr.Range().Filename)
if err != nil {
return err
}
req := EvalExprRequest{
Expr: expr.Range().SliceBytes(src),
ExprRange: expr.Range(),
Ret: ret,
}
if err := c.rpcClient.Call("Plugin.EvalExpr", req, &response); err != nil {
return err
}
if response.Err != nil {
Expand All @@ -101,21 +135,28 @@ func (c *Client) EvaluateExpr(expr hcl.Expression, ret interface{}) error {

// EmitIssueRequest is the interface used to communicate via RPC.
type EmitIssueRequest struct {
Rule *RuleObject
Message string
Location hcl.Range
Meta Metadata
Rule *RuleObject
Message string
Location hcl.Range
Expr []byte
ExprRange hcl.Range
}

// EmitIssue emits attributes to build the issue to the host process
// Note that the passed rule need to be converted to generic objects
// because the custom structure defined in the plugin cannot be sent via RPC.
func (c *Client) EmitIssue(rule Rule, message string, location hcl.Range, meta Metadata) error {
src, err := ioutil.ReadFile(meta.Expr.Range().Filename)
if err != nil {
return err
}

req := &EmitIssueRequest{
Rule: newObjectFromRule(rule),
Message: message,
Location: location,
Meta: meta,
Rule: newObjectFromRule(rule),
Message: message,
Location: location,
Expr: meta.Expr.Range().SliceBytes(src),
ExprRange: meta.Expr.Range(),
}
if err := c.rpcClient.Call("Plugin.EmitIssue", &req, new(interface{})); err != nil {
return err
Expand Down Expand Up @@ -143,3 +184,21 @@ func (*Client) EnsureNoError(err error, proc func() error) error {
return err
}
}

func parseExpression(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) {
if strings.HasSuffix(filename, ".tf") {
return hclsyntax.ParseExpression(src, filename, start)
}

if strings.HasSuffix(filename, ".tf.json") {
return nil, hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "JSON configuration syntax is not supported",
Subject: &hcl.Range{Filename: filename, Start: start, End: start},
},
}
}

panic(fmt.Sprintf("Unexpected file: %s", filename))
}
44 changes: 34 additions & 10 deletions tflint/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package tflint
import (
"encoding/gob"
"errors"
"io/ioutil"
"net"
"net/rpc"
"os"
"testing"

"github.com/google/go-cmp/cmp"
Expand All @@ -19,16 +21,15 @@ type mockServer struct {
}

func (*mockServer) Attributes(req *AttributesRequest, resp *AttributesResponse) error {
expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
*resp = AttributesResponse{Attributes: []*hcl.Attribute{}, Err: diags}
return nil
}

*resp = AttributesResponse{Attributes: []*hcl.Attribute{
*resp = AttributesResponse{Attributes: []*Attribute{
{
Name: req.AttributeName,
Expr: expr,
Expr: []byte("1"),
ExprRange: hcl.Range{
Filename: "example.tf",
Start: hcl.Pos{Line: 1, Column: 1},
End: hcl.Pos{Line: 2, Column: 2},
},
Range: hcl.Range{
Start: hcl.Pos{Line: 1, Column: 1},
End: hcl.Pos{Line: 2, Column: 2},
Expand Down Expand Up @@ -109,7 +110,16 @@ func Test_EvaluateExpr(t *testing.T) {
client, server := startMockServer(t)
defer server.Listener.Close()

expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 1})
file, err := ioutil.TempFile("", "tflint-test-evaluateExpr-*.tf")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
if _, err := file.Write([]byte("1")); err != nil {
t.Fatal(err)
}

expr, diags := hclsyntax.ParseExpression([]byte("1"), file.Name(), hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
t.Fatal(diags)
}
Expand All @@ -136,7 +146,21 @@ func Test_EmitIssue(t *testing.T) {
client, server := startMockServer(t)
defer server.Listener.Close()

if err := client.EmitIssue(&testRule{}, "test", hcl.Range{}, Metadata{}); err != nil {
file, err := ioutil.TempFile("", "tflint-test-evaluateExpr-*.tf")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
if _, err := file.Write([]byte("1")); err != nil {
t.Fatal(err)
}

expr, diags := hclsyntax.ParseExpression([]byte("1"), file.Name(), hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
t.Fatal(diags)
}

if err := client.EmitIssue(&testRule{}, file.Name(), hcl.Range{}, Metadata{Expr: expr}); err != nil {
t.Fatal(err)
}
}
Expand Down

0 comments on commit a1fcc54

Please sign in to comment.